如何比较两个散列?

我试图用下面的代码比较两个 Ruby Hash:

#!/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

屏幕上的输出是来自 file2的完整文件。我知道这些文件是不同的,但是脚本似乎没有发现这一点。

138661 次浏览

还有一种更简单的方法:

require 'fileutils'
FileUtils.cmp(file1, file2)

您可以直接比较哈希表的相等性:

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


您可以将散列转换为数组,然后得到它们的差异:

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}

如果你想知道两个散列之间的区别,你可以这样做:

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"}

这个问题的答案是“ Comparing ruby hashes”。 Rails 为散列添加了一个 diff。它工作得很好。

您可以尝试使用 Hashdiff gem,它允许对散列和散列中的数组进行深度比较。

下面是一个例子:

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]]

If you need a quick and dirty diff between hashes which correctly supports nil 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

Rails 是 不屑一顾diff方法。

For a quick one-liner:

hash1.to_s == hash2.to_s

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

# Gemfile
gem 'awesome_print' # or gem install awesome_print

在你的代码里:

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})

这个想法是使用可怕的打印格式,并差异的输出。这个 diff 并不精确,但是对于调试目的很有用。

... 现在以 模组的形式应用于各种集合类(其中包括散列)。这不是深度检查,但很简单。

# 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

您可以使用一个简单的数组交集,这样您就可以知道每个散列中的不同之处。

    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

将 hash 转换为 _ json 并作为 string 进行比较怎么样

require "json"
h1 = {a: 20}
h2 = {a: "20"}


h1.to_json==h1.to_json
=> true
h1.to_json==h2.to_json
=> false

我开发了这个来比较两个散列是否相等

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

用法:

> 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: {h: 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

下面是深入比较两个 Hash 的算法,它也会比较嵌套数组:

    HashDiff.new(
{val: 1, nested: [{a:1}, {b: [1, 2]}] },
{val: 2, nested: [{a:1}, {b: [1]}] }
).report
# Output:
val:
- 1
+ 2
nested > 1 > b > 1:
- 2

实施方法:

class HashDiff


attr_reader :left, :right


def initialize(left, right, config = {}, path = nil)
@left  = left
@right = right
@config = config
@path = path
@conformity = 0
end


def conformity
find_differences
@conformity
end


def report
@config[:report] = true
find_differences
end


def find_differences
if hash?(left) && hash?(right)
compare_hashes_keys
elsif left.is_a?(Array) && right.is_a?(Array)
compare_arrays
else
report_diff
end
end


def compare_hashes_keys
combined_keys.each do |key|
l = value_with_default(left, key)
r = value_with_default(right, key)
if l == r
@conformity += 100
else
compare_sub_items l, r, key
end
end
end


private


def compare_sub_items(l, r, key)
diff = self.class.new(l, r, @config, path(key))
@conformity += diff.conformity
end


def report_diff
return unless @config[:report]


puts "#{@path}:"
puts "- #{left}" unless left == NO_VALUE
puts "+ #{right}" unless right == NO_VALUE
end


def combined_keys
(left.keys + right.keys).uniq
end


def hash?(value)
value.is_a?(Hash)
end


def compare_arrays
l, r = left.clone, right.clone
l.each_with_index do |l_item, l_index|
max_item_index = nil
max_conformity = 0
r.each_with_index do |r_item, i|
if l_item == r_item
@conformity += 1
r[i] = TAKEN
break
end


diff = self.class.new(l_item, r_item, {})
c = diff.conformity
if c > max_conformity
max_conformity = c
max_item_index = i
end
end or next


if max_item_index
key = l_index == max_item_index ? l_index : "#{l_index}/#{max_item_index}"
compare_sub_items l_item, r[max_item_index], key
r[max_item_index] = TAKEN
else
compare_sub_items l_item, NO_VALUE, l_index
end
end


r.each_with_index do |item, index|
compare_sub_items NO_VALUE, item, index unless item == TAKEN
end
end


def path(key)
p = "#{@path} > " if @path
"#{p}#{key}"
end


def value_with_default(obj, key)
obj.fetch(key, NO_VALUE)
end


module NO_VALUE; end
module TAKEN; end


end


在我的案例中,我希望像 { status: [:collecting, :out_for_delivery] }那样合并属性,所以我这样做了:

    before = attributes.without(*IGNORED_ATTRIBUTES)
after = replacement.attributes
diff = before.map do |key, _|
[key, [before[key], after[key]]] if before[key] != after[key]
end
diff.compact.to_h