11

I have a list of 2D points (a table, imagine the data of a parametric plot shuffled)

I would like to join the points with a line that starts from one of them and always goes to the closest one.

I tried therefore to sort the points doing the following:

  • take out the first element,
  • search the closest in the remaining list
  • bring it to the front
  • recurse

so that I can then use ListLinePlot

As a first step I tried to do it in 1D (yes, in this case a simple sorting is sufficient, but not in the 2D case)

However,I have a problem, because I do not know how to specify that a variable IS a list.

Concretely

BringToFront = 
 Function[{list, pos}, Prepend[list[[pos]], Drop[list, {pos}]]]

BringClosestToFront = 
 Fuction[{list, val}, 
  BringToFront[list, Nearest[list ->Automatic, val]]]

Follow[{}] = {};
Follow[list] = 
 Prepend[list[[1]], 
  Follow[BringClosestToFront[Drop[list, 1], list[[1]]]]]

And the BringClosestToFront is not accepted, with a

Part::partd: Part specification list[[1]] is longer than depth of object. >>

I am also worried of the speed of this recursive solution. Do you thing there may be a way to specify it in a more procedural way (i.e. implement insertion-sort)?

Fabio Dalla Libera
  • 529
  • 1
  • 3
  • 11

4 Answers4

9

FindShortestTour can solve your problem. You need only choose the greedy algorithm. For example, using the same data as image_doctor:

SeedRandom[6];
data = RandomReal[{-10, 10}, {10, 2}];

FindShortestTour[data, Method -> "Greedy"]

{61.2702, {1, 7, 2, 3, 6, 4, 10, 5, 9, 8}}

Show[
  Graphics[{Line[data[[{1, 7, 2, 3, 6, 4, 10, 5, 9, 8}]]], 
     PointSize[Medium], Red, Point[data]}],
  ImageSize -> Small]

[greedy.png]

BTW, the graphics output was generated for me by V9's predictive interface. I didn't write any code for it at all.

m_goldberg
  • 107,779
  • 16
  • 103
  • 257
8

Here is another recursive solution based on Nest using an index to the point in the list from which to start.

order[points_List, index_Integer] :=  
Nest[With[{elem = Nearest[Last@#, Last@First@#]}, 
 {Join[First@#,elem], DeleteCases[Last@#, elem]}] &, 
 {{points[[index]]}, Drop[points, {index}]}, Length@points - 1] // First

It looks more complicated than it actually is ( I know that a more concise implementation is out there ). It begins with a list of the form, {{start point},{other points}}. Then moves the nearest point from other points onto the end of the start point list. It continues using the last element of the extended list to find the nearest element in the list of remaining points.

Using BG's data:

ListLinePlot@order[data, 1]

Mathematica graphics

ListLinePlot@order[data, 5]

Mathematica graphics

As a guide to speed, 10 thousand elements took around 44 seconds:

SeedRandom[6];
data2 = RandomReal[{-10, 10}, {10^4, 2}];

Timing[order[data2, 1];]

{43.8883, Null}

A less readable version which maintains duplicate points and is around 20+% faster is as follows:

order[points_, index_] := 
 Nest[With[{p = 
       First@Position[Last@#, 
         First@Nearest[Last@#, Last@First@#]]}, {Join[First@#, 
       Take[Last@#, p]], Drop[Last@#, p]}] &, {{points[[index]]}, 
    Drop[points, {index}]}, Length@points - 1] // First
image_doctor
  • 10,234
  • 23
  • 40
6

One simple way :

SeedRandom[6];
data = RandomReal[{-10, 10}, {10, 2}] ;

findNearest[list_, point_] := SortBy[list, EuclideanDistance[#, point] &][[1]]

copy = data;
output = NestList[findNearest[copy = DeleteCases[copy, #], #] &, copy[[1]], Length[copy] - 1];

GraphicsColumn[{ListLinePlot[data], ListLinePlot[output]}]

enter image description here

b.gates.you.know.what
  • 20,103
  • 2
  • 43
  • 84
3

Here's a version based on a recursively defined function f:

f[{x_, y_}] := f[{x~Join~y[[#]], Drop[y, #]} &@Nearest[y -> Automatic, Last@x]];
f[{x_, {}}] := x;

data = RandomReal[{-10, 10}, {1000, 2}];
output = f[{{First[data]}, Rest[data]}];
ListLinePlot[output, Mesh -> True]

enter image description here

Simon Woods
  • 84,945
  • 8
  • 175
  • 324