The closest pair of points problem is a common computational geometry problem: given n points, find a pair of points with the smallest distance between them. A naive algorithm of finding distances between all pairs of points and selecting the minimum requires O(n2) time. The problem may be solved in O(n log n) time in a Euclidean space. Rosetta Code has only the brute force solution for Mathematica. I coded up a O(n log n) version, but my implementation is slow, and only beats the O(n2) for n > 10,000 on my machine. How can this code be made faster? Is there a built-in Mathematica function for this?
Here is my O(n2) code:
nearestPairN[
data_] := (*This is 10x faster than \
https://rosettacode.org/wiki/Closest-pair_problem#Mathematica_.2F_\
Wolfram_Language.*)
Last @@ Module[{npt},
MinimalBy[
Table[npt =
N[First@Nearest[Drop[data, i], data[[i]],
1]]; {EuclideanDistance[data[[i]], npt], {data[[i]],
npt}}, {i, Length[data] - 1}], First]]
Here is my O(n log n) code:
closestPair[ptsIn_] := Module[{xP, yP, pts},
(*Top level function.
Sorts the pts by x and by y and then calls closestPairR[] *)
pts = N[ptsIn];
xP = Sort[pts, #1[[1]] < #2[[1]] &];
yP = Sort[pts, #1[[2]] < #2[[2]] &];
closestPairR[xP, yP]
]
closestPairR[xP_, yP_] :=
Module[{n, mid, xL, xR, xm, yL, yR, dL, pairL, dmin, pairMin, yS, nS,
closest, closestP, k, cDist},
(*where xP is P(1).. P(n) sorted by x coordinate, and
yP is P(1).. P(n) sorted by y coordinate (ascending order) *)
n = Length[xP];
If[ n <= 3, (*Brute Force*)
Piecewise[{
{{\[Infinity], {}}, n < 2},
{{EuclideanDistance[xP[[1]], xP[[2]]], {xP[[1]], xP[[2]]}},
n == 2},
{Last@MinimalBy[{
{EuclideanDistance[xP[[1]], xP[[2]]], {xP[[1]], xP[[2]]}},
{EuclideanDistance[xP[[1]], xP[[3]]], {xP[[1]], xP[[3]]}},
{EuclideanDistance[xP[[3]], xP[[2]]], {xP[[3]], xP[[2]]}}
}, First], n == 3}
}],
mid = Ceiling[n/2];
xL = xP[[1 ;; mid]];
xR = xP[[mid + 1 ;; n ]];
xm = xP[[mid]];
yL = Select[yP, #[[1]] <= xm[[1]] &];
yR = Select[yP, #[[1]] > xm[[1]] &];
{dL, pairL} = closestPairR[xL, yL];
{dmin, pairMin} = closestPairR[xR, yR];
If[dL < dmin, {dmin, pairMin} = {dL, pairL}];
yS = Select[yP, Abs[#[[1]] - xm[[1]]] <= dmin &];
nS = Length[yS];
{closest, closestP} = {dmin, pairMin} ;
Table[
k = i + 1;
While[(k <= nS) && (yS[[k, 2]] - yS[[i, 2]] < dmin),
cDist = EuclideanDistance[ yS[[k]], yS[[i]]];
If[cDist < closest,
{closest, closestP} = {cDist, { yS[[k]], yS[[i]]}}
];
k = k + 1]
, {i, 1, nS - 1}];
{closest, closestP}
](*end if*)]
And test code:
pts = RandomReal[1, {10000, 2}];
Timing[nearestPairN[pts]]
Timing[closestPair[pts]]
One sample run gave me:
{1.58398, {{0.696976, 0.770051}, {0.697001, 0.770091}}}
{1.54305, {0.000047247, {{0.696976, 0.770051}, {0.697001, 0.770091}}}}
[Edit] The three submitted solutions are compared below, using RepeatedTiming for n from 10k to 500k. @Carl's solution is the fastest, it uses built-in functions, and is succinct.
DistanceMatrix[]
? $\endgroup$DistanceMatrix[]
is much slower (slower even than my nearestPairN), and seems to build the O(n_^2) array of distances. I particularly want a O(_n log (n) ) implementation. $\endgroup$Table
, then throwing it away. Does usingDo
help any? $\endgroup$Do
to speed it up. Thanks! $\endgroup$