user662650
user662650

Reputation: 186

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

(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)

Task

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?

Upvotes: 0

Views: 1775

Answers (4)

MBo
MBo

Reputation: 80187

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

Upvotes: 2

Lakshya Khatri
Lakshya Khatri

Reputation: 11

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

Upvotes: 1

Simon Goater
Simon Goater

Reputation: 1908

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.

Upvotes: 0

Woodford
Woodford

Reputation: 4449

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.

Upvotes: 0

Related Questions