From: Rob Biedenharn Date: 2011-07-16T02:18:41+09:00 Subject: Re: Comparing hashes based on their keys On Jul 15, 2011, at 12:04 PM, Robert Klemme wrote: > 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 ) ) 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/