Sorting points by their x value is coming up wrong

Hi. Am I making a stupid mistake here? I believe this is a correct usage of sort_by but the result doesn’t seem to be correct.

a = [Geom::Point3d.new(12.333, 0.530369, 25.3869), Geom::Point3d.new(12.3615, 0.530369, 21.1437), 
Geom::Point3d.new(12.3903, 0.530369, 16.8721), Geom::Point3d.new(12.3242, 0.530369, 26.7057), Geom::Point3d.new(12.3332, 0.530369, 25.3588)]

result = a.sort_by{|pt3d| pt3d.x}

=> [Point3d(12.3242, 0.530369, 26.7057), Point3d(12.3332, 0.530369, 25.3588), 
Point3d(12.333, 0.530369, 25.3869), Point3d(12.3615, 0.530369, 21.1437), Point3d(12.3903, 0.530369, 16.8721)]

The third point in result should come before the second point, no?

That’s quite strange. I’ve copied and pasted your code into the Ruby console and I get the correct ordering. Maybe you have some extra lines of code manipulating that list? :thinking:

If I copy from my post I get the same result as in my post.
but…

pts = a.each.collect{|pt3d| pt3d.to_a}
result = pts.sort_by{|pt| pt[0]}

… does work fine.

in 2024

aka

result = a.sort_by(&:x)

is the same as:

result = a.sort { |pt1,pt2| pt1.x == pt2.x }

Which means that the method #== is called upon the Geom::Point3d objects.

The SketchUp API defines it’s own comparison method for Geom::Point3d that compares to within SketchUp’s internal tolerance which is 1/1000th of an inch. So, …

Correct, the answer is NO.

The comparison finds the two values to be identical within the tolerance, so the order is not predicable.

(1) The API doc is incorrect for the Geom::Point3d#to_a method. It does not return an array of Length it returns an array of Float.

  • Comparing values with Float#== does not use SketchUp’s internal tolerance.
  • Comparing values with Length#== does use SketchUp’s internal tolerance.

(2) Again, you can use …

result = pts.sort_by(&:x)

… because the SketchUp API adds #x, #y and #z methods to the Array class.

(3) The following is better than a.each.collect

pts = a.map(&:to_a)

(4) But if you map to arrays of Length, you should get the same weird results (from the opening post) because Length#== does use SketchUp’s internal tolerance.

pts = a.map { |pt| pt.to_a.map(&:to_l) }
result = pts.sort_by(&:x)

(A) Please get in the habit of posting code that we don’t have to scroll horizontally. Ex:

a = [
  Geom::Point3d.new(12.333, 0.530369, 25.3869), 
  Geom::Point3d.new(12.3615, 0.530369, 21.1437),
  Geom::Point3d.new(12.3903, 0.530369, 16.8721),
  Geom::Point3d.new(12.3242, 0.530369, 26.7057),
  Geom::Point3d.new(12.3332, 0.530369, 25.3588)
]

(B) You can save yourself some typing …

a = [
  [12.333, 0.530369, 25.3869], 
  [12.3615, 0.530369, 21.1437],
  [12.3903, 0.530369, 16.8721],
  [12.3242, 0.530369, 26.7057],
  [12.3332, 0.530369, 25.3588]
].map( &Geom::Point3d.method(:new) )
#=> [ Point3d(12.333, 0.530369, 25.3869), Point3d(12.3615, 0.530369, 21.1437),
 Point3d(12.3903, 0.530369, 16.8721), Point3d(12.3242, 0.530369, 26.7057),
 Point3d(12.3332, 0.530369, 25.3588) ]
1 Like

Wow!. Best teacher ever!

1 Like

Ignoring issues with tolerances, to sort only by x, use

result = a.sort_by{ |pt3d| pt3d.x.to_f }

Adding .to_f forces it to be a float, whcih isn’t affected by SketchUp’s tolerance code.

1 Like

Greg’s solution is the best which does not convert the array items to another type. They remain Geom::Point3d objects.