On Jul 15, 2011, at 12:04 PM, Robert Klemme wrote:
> On Fri, Jul 15, 2011 at 5:38 PM, Stefano Mioli <stefano.mioli / gmail.com 
> > wrote:
>> Hi list,
>>    let's say we want to write a method that compares two hashes for  
>> equality
>> based on their keys.
>>
>> Each key is a symbol, and the corrisponding value can either be a  
>> string or
>> another hash with this same properties.
>>
>> Two hashes, let's call them h1 and h2, are to be considered equal if:
>>
>> * they have the same keys
>>
>> * h1[:x] and h2[:x] are both hashes, and they are equal according  
>> to the
>> very same rules you are reading ;)
>>
>> * they are both nil
>
> It seems your spec is not very precise because your bullet list mixes
> AND and OR.  I assume you meant
>
> Two objects o1 and o2 are considered equal for this relation if and  
> only if
>
> ( o1 is nil AND o2 is nil ) OR
> ( o1 is a Hash AND o2 is a Hash AND
>  o1.keys == o2.keys AND
>  for each key k ( o1[k] is not a Hash OR
>                          o2[k] is not a Hash OR
>                          o1[k] equals o2[k] according to this  
> relation ) )

I don't see why you couldn't just add your own method to Hash. (Tested  
on ruby-1.9.2)

class Hash
   def same_key_structure?(other)
     return false unless other.is_a?(Hash)
     return false unless self.keys == other.keys && self.keys.all?{|_| 
_.is_a?(Symbol)}
     self.keys.all? {|_|self[_].is_a?(Hash) ?  
self[_].same_key_structure?(other[_]) : !other[_].is_a?(Hash)}
   end
end

If you really cared about the nil case, you could do:

class NilClass
   def same_key_structure?(other)
     other.nil?
   end
end

But that's probably a stretch and only *you* can decided if it's right  
for the situation.

-Rob

P.S. I'm assuming that you don't consider supersets of keys to be  
"equal" and that you really do care that all the keys are symbols.

>
>> In short, we only care about the values when they are hashes.
>> If they are strings, we don't care whether they are equal or not.
>>
>> To make an example, when fed these two hashes the method should  
>> return true:
>>
>> h1 = {
>>  :one => "one",
>>  :two => "two",
>>  :three => {
>>    :alpha => "alpha",
>>    :bravo => "bravo",
>>    :charlie => "charlie"
>>  }
>> }
>>
>> h2 = {
>>  :one => "whatever",
>>  :two => "hey",
>>  :three => {
>>    :alpha => "zulu",
>>    :bravo => "oscar",
>>    :charlie => "brown"
>>  }
>> }
>>
>> When fed these other two arrays, the method should return false:
>>
>> h3 = h1
>>
>> h4 = {
>>  :one => "one",
>>  :two => "two",
>>  :three => {
>>    :alpha => "alpha",
>>    :bravo => "bravo",
>>  }
>> }
>>
>> The difference is that Ole :charlie is missing in h2.
>> The values don't change to make it clear that we don't care about  
>> them.
>>
>> I came up with the following implementation that seems to work (at  
>> least
>> according to the specs I wrote), but what I would like to ask is if  
>> it could
>> be any simpler/more idiomatic/more elegant.
>>
>> Corner cases that one might spot are also good to know about.
>>
>> def compare(hash1, hash2)
>
> I would rename arguments because these need not be Hashes.
>
>>  args = [hash1, hash2]
>>
>>  return true if args.all? {|h| h.nil?}
>>  return false if args.one? {|h| h.nil?}
>>
>>  hash1.each_key do |k|
>>    values = [hash1[k], hash2[k]]
>>
>>    if values.all? {|h| h.is_a?(Hash)}
>>      return compare(*values)
>>    else
>>      return false if values.one? {|value| value.nil? }
>>    end
>>  end
>>
>>  true
>> end
>
> Hm, I'd probably remove #all?, #any? and the like and code conditions
> directly - especially since you are always dealing with two items
> because it's likely faster.  Your code creates a lot of temporary
> Arrays along the way.
>
> Btw, I believe your implementation misses the case where hash2's key
> set is a superset of hash1's, i.e. contains additional keys.
>
> Kind regards
>
> robert
>
> -- 
> remember.guy do |as, often| as.you_can - without end
> http://blog.rubybestpractices.com/
>

Rob Biedenharn		
Rob / AgileConsultingLLC.com	http://AgileConsultingLLC.com/
rab / GaslightSoftware.com		http://GaslightSoftware.com/