# How to find the number of unordered pairs of 2D coordinate points, whose joining line passes through origin?

## Question:

(Original problem description)

Pair of points

You are given the following

An integer N

A 2D array of length N denoting the points in the 2D coordinate system, that is (x, y)

Determine the number of unordered pairs (i, j) or (j, i) and i != j such that

The straight line connecting the points (A[i][1], A[i][2]) and (A[j][1], A[j][2]) passes through (0, 0)

(Context, this was a coding problem on hacker earth site and I did solve it (bruteforce) method)

My code:

``````def find_pairs(array, size):
li = []
for i in range(size):
for j in range(size):
if (i, j) not in li and (j, i) not in li:
if ((array[i][1] * (array[j][0] - array[i][0])) == ((array[i][0] * (array[j][1] - array[i][1]))):
li.append((i,j))
return len(li)
``````

The math the code uses is, given two points (x1, y1) and (x2, y2), their line passes through the origin if they satisfy the equation (x1 * (y2 – y1)) = (y1 * (x2 – x1))

This code passed half the test cases (which were testing for correct answer) but failed the remaining which had time constraint. I tried to use itertools.combinations but it exceeded the memory limit

Is there any way to write a program with less than N2 time complexity?

If using `itertools.combinations` exceeded your memory limit then you used it incorrectly. The functions in the `itertools` library are generally quite memory efficient because they work with iterators rather than creating complete lists in memory.

``````num_pairs = 0
for i, j in itertools.combinations(array, 2):
if i_j_are_valid_points():  # your formula here
num_pairs += 2          # (i, j) and (j, i) are both valid
``````

Note that this is still O(N^2) – you can’t find all combinations of pairs in less time. But it will run over twice as fast as your solution since: 1) it doesn’t check both (i, j) and (j, i) redundantly; 2) it doesn’t need to traverse the list of already found solutions.

I suspect you can do this in O(NlogN) (merge sort) time, at least in theory. If you convert to polar coordinates, then you’re looking for two points (r_1, theta) and (r_2, theta + pi) so you can just sort them and then process from theta in [0, pi), and compare with those from theta in [pi, 2pi). If you use floating point numbers though, you’ll need to be careful how you compare the two values because floating point values are only stored approximately.

One optimization we can make in your code is not to check previously traversed co-ordinates:

``````def find_pairs(array, size):
count = 0
for i in range(size - 1):
for j in range(i + 1, size):
if ((array[i][1] * (array[j][0] - array[i][0])) == ((array[i][0] * (array[j][1] - array[i][1]))):
count += 1
return count
``````

Put tangent (slope) of line origin-to-point into `Counter` (in general – dictionary with value as counter for old Python without this class). Make separate counter for points with x=0 (vertical line).

After putting all slopes into the map, retieve number of pairs for every slope – `n` points with the same slope give `n*(n-1)/2` pairs

Linear complexity.

``````from collections import Counter
pts = [[1,0],[2,2],[-1,-1],[4,4],[0,2],[-2,0]]
vert = 0
cntr = Counter()
for p in pts:
if p[0]:
cntr[p[1]/p[0]] += 1
else:
vert += 1
res = vert * (vert - 1) // 2
for v in cntr.values():
res += v*(v-1) // 2
print(res)  # 4 pairs
``````

Update: accounts for (0,0):

``````from collections import Counter
pts = [[1,0],[2,2],[-1,-1],[4,4],[0,2],[-2,0],[0,0],[0,0]]
vert = 0
zeros = 0
cntr = Counter()
for p in pts:
if p[0]:
cntr[p[1]/p[0]] += 1
else:
if p[1]:
vert += 1
else:
zeros += 1
res = vert * (vert - 1) // 2
for v in cntr.values():
res += v*(v-1) // 2
res += (len(pts) - zeros) * zeros  + zeros*(zeros-1)//2
print(res) //17 pairs

``````

Potential pitfall – float number comparison might give wrong results for some pairs.
If points are integers, it is possible to use integer tuple `( sign(x)*y/gcd(y,x), abs(x)/gcd(x,y) )` as key

Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.