How do I compare two hashes?

126,417

Solution 1

You can compare hashes directly for equality:

hash1 = {'a' => 1, 'b' => 2}
hash2 = {'a' => 1, 'b' => 2}
hash3 = {'a' => 1, 'b' => 2, 'c' => 3}

hash1 == hash2 # => true
hash1 == hash3 # => false

hash1.to_a == hash2.to_a # => true
hash1.to_a == hash3.to_a # => false


You can convert the hashes to arrays, then get their difference:

hash3.to_a - hash1.to_a # => [["c", 3]]

if (hash3.size > hash1.size)
  difference = hash3.to_a - hash1.to_a
else
  difference = hash1.to_a - hash3.to_a
end
Hash[*difference.flatten] # => {"c"=>3}

Simplifying further:

Assigning difference via a ternary structure:

  difference = (hash3.size > hash1.size) \
                ? hash3.to_a - hash1.to_a \
                : hash1.to_a - hash3.to_a
=> [["c", 3]]
  Hash[*difference.flatten] 
=> {"c"=>3}

Doing it all in one operation and getting rid of the difference variable:

  Hash[*(
  (hash3.size > hash1.size)    \
      ? hash3.to_a - hash1.to_a \
      : hash1.to_a - hash3.to_a
  ).flatten] 
=> {"c"=>3}

Solution 2

You can try the hashdiff gem, which allows deep comparison of hashes and arrays in the hash.

The following is an example:

a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
b = {a:{y:3}, b:{y:3, z:30}}

diff = HashDiff.diff(a, b)
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]

Solution 3

If you want to get what is the difference between two hashes, you can do this:

h1 = {:a => 20, :b => 10, :c => 44}
h2 = {:a => 2, :b => 10, :c => "44"}
result = {}
h1.each {|k, v| result[k] = h2[k] if h2[k] != v }
p result #=> {:a => 2, :c => "44"}

Solution 4

Rails is deprecating the diff method.

For a quick one-liner:

hash1.to_s == hash2.to_s

Solution 5

You could use a simple array intersection, this way you can know what differs in each hash.

    hash1 = { a: 1 , b: 2 }
    hash2 = { a: 2 , b: 2 }

    overlapping_elements = hash1.to_a & hash2.to_a

    exclusive_elements_from_hash1 = hash1.to_a - overlapping_elements
    exclusive_elements_from_hash2 = hash2.to_a - overlapping_elements
Share:
126,417
dennismonsewicz
Author by

dennismonsewicz

Updated on May 28, 2020

Comments

  • dennismonsewicz
    dennismonsewicz almost 4 years

    I am trying to compare two Ruby Hashes using the following code:

    #!/usr/bin/env ruby
    
    require "yaml"
    require "active_support"
    
    file1 = YAML::load(File.open('./en_20110207.yml'))
    file2 = YAML::load(File.open('./locales/en.yml'))
    
    arr = []
    
    file1.select { |k,v|
      file2.select { |k2, v2|
        arr << "#{v2}" if "#{v}" != "#{v2}"
      }
    }
    
    puts arr
    

    The output to the screen is the full file from file2. I know for a fact that the files are different, but the script doesn't seem to pick it up.

  • dennismonsewicz
    dennismonsewicz about 13 years
    Is there anyway to get the differences between the two?
  • PJP
    PJP over 12 years
    That only is meaningful if you need the hashes to be identical on the disk. Two files that are different on disk because the hash elements are in different orders, can still contain the same elements, and will be equal as far as Ruby is concerned once they are loaded.
  • davetapley
    davetapley over 11 years
    I had some fairly deep hashes causing test failures. By replacing the got_hash.should eql expected_hash with HashDiff.diff(got_hash, expected_hash).should eql [] I now get output which shows exactly what I need. Perfect!
  • ohaleck
    ohaleck over 9 years
    Hashes can be of same size, but contain different values. In such case Both hash1.to_a - hash3.to_a and hash3.to_a - hash1.to_a may return nonempty values though hash1.size == hash3.size. The part after EDIT is valid only if hashes are of different size.
  • Jeff Wigal
    Jeff Wigal over 9 years
    Wow, HashDiff is awesome. Made quick work of trying to see what has changed in a huge nested JSON array. Thanks!
  • Gene
    Gene about 9 years
    Nice, but should have quit while ahead. A.size > B.size doesn't necessarily mean A includes B. Still need to take the union of symmetric differences.
  • Andres
    Andres almost 9 years
    Diff method is deprecated starting from Rails versions newer than v4.0.2.
  • Alain
    Alain almost 9 years
    Your gem is awesome! Super helpful when writing specs involving JSON manipulations. Thx.
  • PJP
    PJP over 7 years
    I always forget about this. There are a lot of equality checks that are made easy using to_s.
  • aidan
    aidan about 7 years
    Directly comparing the output of .to_a will fail when equal hashes have keys in a different order: {a:1, b:2} == {b:2, a:1} => true, {a:1, b:2}.to_a == {b:2, a:1}.to_a => false
  • aidan
    aidan about 7 years
    It will fail when equal hashes have keys in a different order: {a:1, b:2} == {b:2, a:1} => true, {a:1, b:2}.to_s == {b:2, a:1}.to_s => false
  • JeremyKun
    JeremyKun about 7 years
    what's the purpose of flatten and *? Why not just Hash[A.to_a - B.to_a]?
  • Oliver Benning
    Oliver Benning over 6 years
    or difference.to_h
  • Dave Morse
    Dave Morse almost 6 years
    Which is a feature! :D
  • David Bodow
    David Bodow almost 6 years
    My experience with HashDiff has been that it works really well for small hashes but the diff speed doesn't seem to scale well. Worth benchmarking your calls to it if you expect it may get fed two large hashes and making sure that the diff time is within your tolerance.
  • Victor
    Victor over 4 years
    @ohaleck You are right! That's why I prefer to use: hash1.to_a - hash2.to_a | hash2.to_a - hash1.to_a. Please take a look at my answer => stackoverflow.com/questions/4928789/how-do-i-compare-two-has‌​hes/…
  • Eric Walker
    Eric Walker almost 4 years
    Using the use_lcs: false flag can significantly speed up comparisons on large hashes: Hashdiff.diff(b, a, use_lcs: false)