Hashes, Sets, and eql?

A

Aaron Patterson

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])
=> # said:
b = Set.new([1,2])
=> # said:
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:
=> true
 
M

MonkeeSage

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
 
A

Aaron Patterson

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 (==).

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
 
M

MonkeeSage

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.

Regards,
Jordan
 
P

Phrogz

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

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.
 
M

MonkeeSage

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
 
A

Aaron Patterson

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

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top