[?] problem using set with hash objects

I'm using set in the ruby standard library to produce collections of unique objects from enumerable objects with duplicates but it's doesn't appear to work with hash objects.

$ ruby --version
ruby 1.8.5 (2006-12-25 patchlevel 12) [i686-darwin8.9.1]
$ irb
irb(main):001:0> require 'set'
=> true
irb(main):002:0> a = [1,1,2,3]
=> [1, 1, 2, 3]
irb(main):003:0> b = [{:a1 => "123"}, {:a1 => "123"}, {:b1 => "123"}]
=> [{:a1=>"123"}, {:a1=>"123"}, {:b1=>"123"}]
irb(main):004:0> seta = a.to_set
=> #<Set: {1, 2, 3}>
irb(main):005:0> setb = b.to_set
=> #<Set: {{:a1=>"123"}, {:a1=>"123"}, {:b1=>"123"}}>
irb(main):006:0> b[0] == b[1]
=> true

Am I doing something wrong?

Alle lunedì 17 settembre 2007, Stephen Bannasch ha scritto:

I'm using set in the ruby standard library to produce collections of
unique objects from enumerable objects with duplicates but it's
doesn't appear to work with hash objects.

$ ruby --version
ruby 1.8.5 (2006-12-25 patchlevel 12) [i686-darwin8.9.1]
$ irb
irb(main):001:0> require 'set'
=> true
irb(main):002:0> a = [1,1,2,3]
=> [1, 1, 2, 3]
irb(main):003:0> b = [{:a1 => "123"}, {:a1 => "123"}, {:b1 => "123"}]
=> [{:a1=>"123"}, {:a1=>"123"}, {:b1=>"123"}]
irb(main):004:0> seta = a.to_set
=> #<Set: {1, 2, 3}>
irb(main):005:0> setb = b.to_set
=> #<Set: {{:a1=>"123"}, {:a1=>"123"}, {:b1=>"123"}}>
irb(main):006:0> b[0] == b[1]
=> true

Am I doing something wrong?

According to the ri documentation, Set internally stores items in a hash.
Because of this, it uses the eql? and hash methods, and not ==, to test
objects for equality. Hash#eql? (actually, Kernel#eql?) only returns true if
two objects are the same object. Since b[0] and b[1] are different objects,
Set considers them not equal, and thus stores them both. If you put the same
hash in two places of the array you convert to a set, only one of them will
be kept:

irb: 001> require 'set'
true
irb: 002> h = {'a' => 1, 'b' => 2}
{"a"=>1, "b"=>2}
irb: 003> a = [h, {'c' => 3}, h]
[{"a"=>1, "b"=>2}, {"c"=>3}, {"a"=>1, "b"=>2}]
irb: 004> a.to_set.size
2
irb: 005> p a.to_set
#<Set: {{"a"=>1, "b"=>2}, {"c"=>3}}>

Other classes, instead, provide their own definition of eql?, which leads to
different (often less surprising) results. For instance, Array#eql? returns
true if the two arrays have the same elements. String do the same.

I hope this helps

Stefano