# Duplicate Points in Array

I’m looking for a quick and efficient algorithm for checking if there are duplicate points in an array of points so I don’t try to create a face and get this error:

`Error: #<ArgumentError: Duplicate points in array>`

An example of an array with four points might be:

[0.0, 0.0, 97.125], [0.0, 0.0, 97.125], [0.0, 5.5, 97.125], [0.0, 5.5, 97.125]

Note that this array actually has two duplicate pairs, if even one duplicate pair exists I need to flag the array as such.

``````ary == ary.uniq
``````

edited: no need for length…

john

2 Likes

`uniq` with no block doesn’t work for points since it uses the `eql?` comparison under the hood.

``````a = [
Geom::Point3d.new(1,2,1),
Geom::Point3d.new(1,2,1),
Geom::Point3d.new(3,3,3),
Geom::Point3d.new(6,6,1)
]
a.uniq
# => [Point3d(1, 2, 1), Point3d(1, 2, 1), Point3d(3, 3, 3), Point3d(6, 6, 1)]
``````

If you have point objects you can temporarely convert them to array objects for the comparison:

``````a.uniq { |p| p.to_a }
``````

Or shorter:

``````a.uniq(&:to_a)
``````
1 Like

Beware… Your title asks about points. Although the API allows 3 element arrays to be used as point coordinate data in many of it’s method arguments, they are not actually points objects.

``````a =[
[0.0, 0.0, 97.125], [0.0, 0.0, 97.125], [0.0, 5.5, 97.125], [0.0, 5.5, 97.125]
]
``````

Plain Ruby will see the first two members as the same because `Array#uniq` uses #eql? to compare members which for `Array#eql?` sees two arrays as equivalent if they contain the same exact core numeric data (ie, Float or Integer values.)

``````a == a.uniq
#=> false
a[0] == a[1]
#=> true
``````

However, when the members are actual point objects the result is different …

``````a.map!{|e| Geom::Point3d.new(e) }
#=> [
#   Point3d(0, 0, 97.125), Point3d(0, 0, 97.125),
#   Point3d(0, 5.5, 97.125), Point3d(0, 5.5, 97.125)
# ]
a[0] == a[1]
#=> true, because the API overrode Geom::Point3d#==
a[0].eql?(a[1])
#=> false, because the API did not override Geom::Point3d#eql?
a == a.uniq
#=> true, because again Array#uniq is using Object#eql?
# ... which the API did not override for the Geom::Point3d class.
``````

So, it might be best if you use a custom method to uniquify a list of points …

``````  def unique_point_array(*args)
if args.size == 1 && args[0].is_a?(Array) &&
args[0].all? {|arg| !arg.is_a?(Numeric) }
args.flatten!
end
# Create a copy of the array mapped to points:
ary = args.map do |obj|
next obj if obj.is_a?(Geom::Point3d)
next nil unless obj.respond_to?(:to_a)
Geom::Point3d.new(obj.to_a[0..2]) # Array | Geom::Vector3d
end
ary.compact! # removes nil references
ary.delete_if do |pt|
# Compare pt against the remainder of the array, removing
# the pt immediately each iteration if block returns true:
ary[ary.index(pt)+1..-1].any? { |other|
# Use API's Geom::Point3d#== to compare within tolerance:
pt == other
}
end
end
``````

Now you still need to be sure that you pass a minimum of three points or an array of 3 points to the `#add_face` method.

``````ents = group.entities
``````

Now, assume you have an array of mixed objects (numeric arrays, points or vectors):

``````ents.add_face(unique_point_array(array))
``````

Or, if the objects were not in an array, you can pass as a list …

``````ents.add_face(
unique_point_array(pt1,[0.0, 0.0, 97.125],vec2,pt3,[0.0, 5.5, 97.125])
)
``````

AND … if you wish to pare it down more … and not allow a list of parameters, …
ie, require 1 array argument of arrays, points or vectors …

``````  # Takes only 1 Array argument of (arrays, points or vectors)
def unique_point_array(args)
# Create a copy of the array mapped to points:
ary = args.select {|arg| arg.respond_to?(:to_a) }.map do |arg|
Geom::Point3d.new(arg.to_a[0..2]) # Array | Point3d | Vector3d
end
ary.delete_if do |pt|
ary[ary.index(pt)+1..-1].any? { |other| pt == other }
end
end
``````

2 Likes