Hey everyone!
I've been working with sets and I need to use them as a key in a hash.
Unfortunately, it looks like Sets are implemented as a Hash (which makes
sense), but it looks like Set#eql? calls eql? on the underlying Hash.
This seems bad to me. It means that I can't make two sets eql? to each
other.
Take this irb session for example:
>> require 'set'
=> true
>> a = Set.new([1,2])
=> #<Set: {1, 2}>
>> b = Set.new([1,2])
=> #<Set: {1, 2}>
>> a.eql?(b)
=> false
I understand that sets are unordered, but it still seems weird to me
that the preceding code won't work. My workaround(?) for now is
converting the set to an array and sorting the array by hash key, and
keying on that:
>> a.to_a.sort_by { |x| x.hash }.eql?(b.to_a.sort_by { |x| x.hash })
=> true
···
--
Aaron Patterson
http://tenderlovemaking.com/
Hi Aaron,
Set#eql? is probably just Object#eql?, which, as I understand it would
compare the actual objects (not their contents). What you want is a
"shallow" comparison (==).
irb(main):002:0> require 'set'
=> true
irb(main):003:0> a = Set.new([1,2])
=> #<Set: {1, 2}>
irb(main):004:0> b = Set.new([1,2])
=> #<Set: {1, 2}>
irb(main):005:0> a == b
=> true
Regards,
Jordan
Nope. I don't want a shallow comparison. I need to use my sets as hash
keys, so I need .eql? and .hash. Set calls eql? on the internal hash object.
From the set source:
def hash # :nodoc:
@hash.hash
end
def eql?(o) # :nodoc:
return false unless o.is_a?(Set)
@hash.eql?(o.instance_eval{@hash})
end
···
On Sat, Nov 24, 2007 at 03:55:00PM +0900, MonkeeSage wrote:
Hi Aaron,
Set#eql? is probably just Object#eql?, which, as I understand it would
compare the actual objects (not their contents). What you want is a
"shallow" comparison (==).
--
Aaron Patterson
http://tenderlovemaking.com/
And eql? implies a.hash == b.hash. That is an object comparison, since
Object#hash gives a unique value for every object like Object#id. What
you need is shallow comparison of values rather than objects: a == b.
I'm not sure how to easily make Hash do that.
Regards,
Jordan
···
On Nov 24, 1:25 am, Aaron Patterson <aa...@tenderlovemaking.com> wrote:
Nope. I don't want a shallow comparison. I need to use my sets as hash
keys, so I need .eql? and .hash. Set calls eql? on the internal hash object.
Not always true:
irb(main):001:0> a1 = [:a,1,'foo']
=> [:a, 1, "foo"]
irb(main):002:0> a2 = [:a,1,'foo']
=> [:a, 1, "foo"]
irb(main):003:0> a1.hash
=> 876036089
irb(main):004:0> a2.hash
=> 876036089
This discussion (using Hashes as hash or set keys, and the
requirements and possible implementations of of eql? for this to work
as sometimes desired) occurred in the last month or two on this list.
···
On Nov 24, 4:35 am, MonkeeSage <MonkeeS...@gmail.com> wrote:
On Nov 24, 1:25 am, Aaron Patterson <aa...@tenderlovemaking.com> > wrote:
> Nope. I don't want a shallow comparison. I need to use my sets as hash
> keys, so I need .eql? and .hash. Set calls eql? on the internal hash object.
And eql? implies a.hash == b.hash. That is an object comparison, since
Object#hash gives a unique value for every object like Object#id. What
you need is shallow comparison of values rather than objects: a == b.
I'm not sure how to easily make Hash do that.
Not always true:
[...]
Point taken. But still should not be relied on.
This discussion (using Hashes as hash or set keys, and the
requirements and possible implementations of of eql? for this to work
as sometimes desired) occurred in the last month or two on this list.
Thanks, found it. [ http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/572e8d8b01a7d24e/c5f532d868afab05
].
So it seems the only way to do it is something like...
class Set
def hash
to_a.sort.hash
end
def eql?(o)
return false unless o.is_a?(Set)
@hash == o.instance_eval{@hash}
end
end
...which is basically what Aaron was already doing.
Seems like there really should be a way to index keys on value rather
than identity (even if it's not the default or requires a separate
class like someone on the other thread mentioned that Smalltalk has).
Regards,
Jordan
···
On Nov 24, 8:34 am, Phrogz <phr...@mac.com> wrote:
Yes. I guess my point is this: given the behavior of eql? and hash on
Array, would you expect the same behavior from Set? I did, and found it
surprising when they didn't behave the same way.
Now, I can understand an argument against having the same behavior since Sets
are unordered. But if that is the case, then why even implement .eql?
and .hash on the Set class? Since those methods are implemented on Set,
it leads me to believe that the original intent was for .eql? and .hash
to behave the same way as on Array.
···
On Sat, Nov 24, 2007 at 11:35:05PM +0900, Phrogz wrote:
On Nov 24, 4:35 am, MonkeeSage <MonkeeS...@gmail.com> wrote:
> On Nov 24, 1:25 am, Aaron Patterson <aa...@tenderlovemaking.com> > > wrote:
>
> > Nope. I don't want a shallow comparison. I need to use my sets as hash
> > keys, so I need .eql? and .hash. Set calls eql? on the internal hash object.
>
> And eql? implies a.hash == b.hash. That is an object comparison, since
> Object#hash gives a unique value for every object like Object#id. What
> you need is shallow comparison of values rather than objects: a == b.
> I'm not sure how to easily make Hash do that.
Not always true:
irb(main):001:0> a1 = [:a,1,'foo']
=> [:a, 1, "foo"]
irb(main):002:0> a2 = [:a,1,'foo']
=> [:a, 1, "foo"]
irb(main):003:0> a1.hash
=> 876036089
irb(main):004:0> a2.hash
=> 876036089
--
Aaron Patterson
http://tenderlovemaking.com/