... or alternatively an Array which prevents duplicate entries.
Is there some kind of object in Ruby which:
As far as I can tell, an Array supports points 1, 3 and 4; while a Set supports 1, 2 and 3 (but not 4). And a SortedSet won't do, because my entries don't implement <=>.
A Quick Intro to Ruby's Set (3 Part Series) All objects in a Set are guaranteed unique. Objects in a Set are not ordered. Sets are built on top of Hashes for super-fast object lookup.
Set implements a collection of unordered values with no duplicates. This is a hybrid of Array's intuitive inter-operation facilities and Hash's fast lookup. Set is easy to use with Enumerable objects (implementing each ).
Ruby has a Set collection that functions very similarly to an array, but with a few crucial differences: All objects in a Set are guaranteed unique (arrays can have duplicates) Objects in a Set are not ordered (arrays are ordered by index)
As of Ruby 1.9, the built-in Hash
object preserves insertion order. For example:
h = {}
h[:z] = 1
h[:b] = 2
h[:a] = 3
h[:x] = 0
p h.keys #=> [:z, :b, :a, :x]
h.delete :b
p h.keys #=> [:z, :a, :x]
h[:b] = 1
p h.keys #=> [:z, :a, :x, :b]
So, you can set any value (like a simple true
) for any key and you now have an ordered set. You can test for a key using either h.key?(obj)
or, if you always set each key to have a truthy value, just h[obj]
. To remove a key, use h.delete(obj)
. To convert the ordered set to an array, use h.keys
.
Because the Ruby 1.9 Set
library happens to be built upon a Hash currently, you can currently use Set
as an ordered set. (For example, the to_a
method's implementation is just @hash.keys
.) Note, however, that this behavior is not guaranteed by that library, and might change in the future.
require 'set'
s = Set[ :f, :o, :o, :b, :a, :r ] #=> #<Set: {:f, :o, :b, :a, :r}>
s << :z #=> #<Set: {:f, :o, :b, :a, :r, :z}>
s.delete :o #=> #<Set: {:f, :b, :a, :r, :z}>
s << :o #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s << :o #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s << :f #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s.to_a #=> [:f, :b, :a, :r, :z, :o]
There isn't one as far as I know, and Set by its mathematical nature is meant to be unordered (or at least, implementationally, meant not to guarantee order - in fact its usually implemented as a hash table so it does mess up order).
However, it's not hard to either extend array directly or subclass it to do this. I just tried it out and this works:
class UniqueArray < Array
def initialize(*args)
if args.size == 1 and args[0].is_a? Array then
super(args[0].uniq)
else
super(*args)
end
end
def insert(i, v)
super(i, v) unless include?(v)
end
def <<(v)
super(v) unless include?(v)
end
def []=(*args)
# note: could just call super(*args) then uniq!, but this is faster
# there are three different versions of this call:
# 1. start, length, value
# 2. index, value
# 3. range, value
# We just need to get the value
v = case args.size
when 3 then args[2]
when 2 then args[1]
else nil
end
super(*args) if v.nil? or not include?(v)
end
end
Seems to cover all the bases. I used OReilly's handy Ruby Cookbook as a reference - they have a recipe for "Making sure a sorted array stays sorted" which is similar.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With