From: Josh Cheek Date: 2011-07-16T02:31:55+09:00 Subject: Re: Comparing hashes based on their keys --90e6ba53aa7076177404a81f068c Content-Type: text/plain; charset=ISO-8859-1 On Fri, Jul 15, 2011 at 10:38 AM, 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 > > 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) > 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 > > > Should the code turn out to be unreadable, I posted it here[0] too. > > [0] = https://gist.github.com/**1084916 > > > Thanks in advance. > > -- > Stefano > > Looks like you've probably got a bug. You explicitly return in your code, causing it to stop evaluating after the first hash: def compare(hash1, hash2) 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 h1 = { :one => "one", :two => { :alpha => "alpha" }, :three => { :alpha => "alpha" }, } h2 = h1.merge :three => { :bravo => 'bravo' } h1 # => {:one=>"one", :two=>{:alpha=>"alpha"}, :three=>{:alpha=>"alpha"}} h2 # => {:one=>"one", :two=>{:alpha=>"alpha"}, :three=>{:bravo=>"bravo"}} compare h1, h2 # => true RUBY_VERSION # => "1.9.2" --90e6ba53aa7076177404a81f068c--