How to compare two hashes?

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 

Exit to the screen is a complete file from a file2. I know the files are different, but the script does not seem to pick it up.

+96
ruby hash
Feb 08 2018-11-11T00:
source share
13 answers

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 hashes to arrays and 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} 



Further simplification:

The purpose of the difference through a triple 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} 

Performing all this 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} 
+148
Feb 08 2018-11-11T00:
source share

You can try hashdiff gem, which allows you to more deeply compare 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]] 
+32
Jun 22 '12 at 17:53
source share

If you want to know what the difference between the two hashes is, 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"} 
+15
Feb 08 2018-11-11T00:
source share

Rails depreciates the diff method.

For fast, single-line image:

 hash1.to_s == hash2.to_s 
+10
Mar 05 '14 at 15:31
source share

You can use a simple intersection of the array, so you can know what is different 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 
+4
Nov 17 '16 at 16:13
source share

I had the same problem and sent a pull request to the rails

  • Works with a deeply nested hash
  • Works with hash arrays

https://github.com/elfassy/rails/commit/5f3410e04fe8c4d4745397db866c9633b80ccec6

+1
Apr 08 '13 at
source share

If you need a quick and dirty difference between hashes that correctly supports zero in values, you can use something like

 def diff(one, other) (one.keys + other.keys).uniq.inject({}) do |memo, key| unless one.key?(key) && other.key?(key) && one[key] == other[key] memo[key] = [one.key?(key) ? one[key] : :_no_key, other.key?(key) ? other[key] : :_no_key] end memo end end 
+1
Oct 04 '13 at 14:50
source share

If you want a nicely formatted diff, you can do this:

 # Gemfile gem 'awesome_print' # or gem install awesome_print 

And in your code:

 require 'ap' def my_diff(a, b) as = a.ai(plain: true).split("\n").map(&:strip) bs = b.ai(plain: true).split("\n").map(&:strip) ((as - bs) + (bs - as)).join("\n") end puts my_diff({foo: :bar, nested: {val1: 1, val2: 2}, end: :v}, {foo: :bar, n2: {nested: {val1: 1, val2: 3}}, end: :v}) 

The idea is to use great printing to format and distinguish between output. The difference will not be accurate, but it is useful for debugging purposes.

+1
Oct 10 '14 at 10:26
source share

... and now in the form of module for application to many collection classes (among them Hash). This is not an in-depth inspection, but it is simple.

 # Enable "diffing" and two-way transformations between collection objects module Diffable # Calculates the changes required to transform self to the given collection. # @param b [Enumerable] The other collection object # @return [Array] The Diff: A two-element change set representing items to exclude and items to include def diff( b ) a, b = to_a, b.to_a [a - b, b - a] end # Consume return value of Diffable#diff to produce a collection equal to the one used to produce the given diff. # @param to_drop [Enumerable] items to exclude from the target collection # @param to_add [Enumerable] items to include in the target collection # @return [Array] New transformed collection equal to the one used to create the given change set def apply_diff( to_drop, to_add ) to_a - to_drop + to_add end end if __FILE__ == $0 # Demo: Hashes with overlapping keys and somewhat random values. Hash.send :include, Diffable rng = Random.new a = (:a..:q).to_a.reduce(Hash[]){|h,k| h.merge! Hash[k, rng.rand(2)] } b = (:i..:z).to_a.reduce(Hash[]){|h,k| h.merge! Hash[k, rng.rand(2)] } raise unless a == Hash[ b.apply_diff(*b.diff(a)) ] # change b to a raise unless b == Hash[ a.apply_diff(*a.diff(b)) ] # change a to b raise unless a == Hash[ a.apply_diff(*a.diff(a)) ] # change a to a raise unless b == Hash[ b.apply_diff(*b.diff(b)) ] # change b to b end 
+1
Dec 31 '15 at 3:35
source share

The answer " Comparing ruby ​​hashes" was answered. Rails adds a diff method to hashes. It works well.

0
Dec 27 '11 at 3:55
source share

how about converting both hashes to to_json and comparing as a string? but given the fact that

 require "json" h1 = {a: 20} h2 = {a: "20"} h1.to_json==h1.to_json => true h1.to_json==h2.to_json => false 
0
Jul 29 '19 at 16:59
source share

I designed this to compare if two hashes are equal

 def hash_equal?(hash1, hash2) array1 = hash1.to_a array2 = hash2.to_a (array1 - array2 | array2 - array1) == [] end 

Using:

 > hash_equal?({a: 4}, {a: 4}) => true > hash_equal?({a: 4}, {b: 4}) => false > hash_equal?({a: {b: 3}}, {a: {b: 3}}) => true > hash_equal?({a: {b: 3}}, {a: {b: 4}}) => false > hash_equal?({a: {b: {c: {d: {e: {f: {g: {marino: 1}}}}}}}}, {a: {b: {c: {d: {e: {f: {g: {h: 1}}}}}}}}) => true > hash_equal?({a: {b: {c: {d: {e: {f: {g: {marino: 1}}}}}}}}, {a: {b: {c: {d: {e: {f: {g: {h: 2}}}}}}}}) => false 
0
09 Sep '19 at 23:27
source share

What about a different, simpler approach:

 require 'fileutils' FileUtils.cmp(file1, file2) 
-3
Feb 08 '11 at 1:50
source share