I'm trying to create an algorithm that finds the closest pair of points (2D) within a set of points. I'm using a divide and conquer method which is explained here Closest Pair of Points Algorithm.
However, my algorithm is really the best when it comes to huge sets of points, it takes a lot more time than it should.
Any idea on how to optimize my code?
from timeit import timeit
from sys import argv
from geo.point import Point
def load_instance(filename):
"""
loads .mnt file.
returns list of points.
"""
with open(filename, "r") as instance_file:
points = [Point((float(p[0]), float(p[1]))) for p in (l.split(',') for l in instance_file)]
return points
def distance_points(point1, point2):
p1_x = point1.coordinates[0]
p1_y = point1.coordinates[1]
p2_x = point2.coordinates[0]
p2_y = point2.coordinates[1]
return (p1_x - p2_x) \
* (p1_x - p2_x) \
+ (p1_y - p2_y) \
* (p1_y - p2_y)
def brute(points):
d_min = float('inf')
n = len(points)
for i, point1 in enumerate(points):
for point2 in points[i+1:]:
d = distance_points(point1, point2)
if d < d_min:
d_min = d
p1 = point1
p2 = point2
return p1, p2, d_min
def closest_pair(points_x, points_y):
n = len(points_x)
if n <= 3:
if n == 2: return points_x[0], points_x[1], distance_points(points_x[0], points_x[1])
return brute(points_x)
mid = n // 2
points_x_left = points_x[:mid]
set_x_left = set(points_x_left)
points_x_right = points_x[mid:]
midpoint = points_x[mid].coordinates[0]
points_y_left = []
points_y_right = []
for x in points_y:
if x in set_x_left:
points_y_left.append(x)
else:
points_y_right.append(x)
(p1, q1, d1) = closest_pair(points_x_left, points_y_left)
(p2, q2, d2) = closest_pair(points_x_right, points_y_right)
if d1 <= d2:
d = d1
pair_min = (p1, q1)
else:
d = d2
pair_min = (p2, q2)
(p3, q3, d3) = closest_pair_boundary(points_x, points_y, d, pair_min)
if d <= d3:
return pair_min[0], pair_min[1], d
else:
return p3, q3, d3
def closest_pair_boundary(points_x, points_y, d, pair_min):
global somme
somme +=1
n = len(points_x)
midx = points_x[n//2].coordinates[0]
s_y = [x for x in points_y if midx - d <= x.coordinates[0] <= midx + d]
d_min = d
m = len(s_y)
for i, point_i in enumerate(s_y[:-1]):
for point_j in s_y[i+1:min(m, i+7)]:
p, q = point_i, point_j
dist = distance_points(p,q)
if dist < d_min:
pair_min = p, q
d_min = dist
return pair_min[0], pair_min[1], d_min
def print_solution(points):
points_x = sorted(points, key=lambda p: p.coordinates[0])
points_y = sorted(points, key=lambda p: p.coordinates[1])
p1, p2, d_min = closest_pair(points_x, points_y)
print(f'{p1}; {p2}')
My "Point" class if pretty classic, two coordinates and a function that calculates and returns the distance between two points.
I'm using search in a set instead of a comparison because it proves more effective. Still, my code could do better. Any help? Maybe there are algorithms that are better suited for bigger sets of data? Or maybe some functions I've used don't work well with big sets or big lists. I'm just looking for any insights really.
(I can't import any libraries)
EDIT : Changed distance function, doesn't return square root now.
Use of enumerate
instead of relying on ranges.
Profiler shows most time spent on closest_pair_boundary
function. Still room for improvement.
geo
? \$\endgroup\$zip
instead, and do not iterate usingrange
and then using indexing to get the element, instead you should iterate over thelist
and get the element directly, with list slicing if necessary. \$\endgroup\$sqrt()
can be slow, so don't use the actual distance (e.g., sqrt(dx2 + dy2)), use the square of the distance (e.g., dx2 + dy2). That could save lots ofsqrt()
calls. Take a look at "line sweep" algorithms. \$\endgroup\$closest_points_boundary
. \$\endgroup\$