From: Robert Klemme Date: 2011-07-16T01:04:56+09:00 Subject: Re: Comparing hashes based on their keys On Fri, Jul 15, 2011 at 5:38 PM, Stefano Mioli 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 ) ) > 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/