比较红宝石的哈希

Che*_*sea 32 ruby hash comparison

可能重复:
如何比较两个哈希值?

我有两个红宝石哈希(基本上是模型),我试图找到它们之间的差异,一个是对象的旧实例,另一个具有分配给某些属性的新值.我正在尝试确定哪些键已经更改,但似乎没有任何内置于Hash中的内容.我可以想到一些蛮力的解决方案,但想知道是否有一个优雅的解决方案.

理想情况下,我需要能够采用两个哈希:

element1 = {:name => "Original", :description => "The original one!"}
element2 = {:name => "Original", :description => "The new one!"}
Run Code Online (Sandbox Code Playgroud)

并能够比较/区分它们并得到像这样的东西:

{:description => "The new one!"}
Run Code Online (Sandbox Code Playgroud)

现在,我真正想到的是在一个哈希中迭代键并将该键的值与第二个哈希中的相应键进行比较,但这似乎太强暴了.

有任何想法吗?非常感谢!

Pet*_*umm 34

这是科林的一个稍微修改过的版本.

class Hash
  def diff(other)
    (self.keys + other.keys).uniq.inject({}) do |memo, key|
      unless self[key] == other[key]
        if self[key].kind_of?(Hash) &&  other[key].kind_of?(Hash)
          memo[key] = self[key].diff(other[key])
        else
          memo[key] = [self[key], other[key]] 
        end
      end
      memo
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

它可以进入哈希值,从而提高效率

{a: {c: 1, b: 2}, b: 2}.diff({a: {c: 2, b: 2}})
Run Code Online (Sandbox Code Playgroud)

回报

{:a=>{:c=>[1, 2]}, :b=>[2, nil]}
Run Code Online (Sandbox Code Playgroud)

代替

{:a=>[{:c=>1, :b=>2}, {:c=>2, :b=>2}], :b=>[2, nil]}
Run Code Online (Sandbox Code Playgroud)

好主意科林

这里是如何将差异应用于原始哈希

  def apply_diff!(changes, direction = :right)
    path = [[self, changes]]
    pos, local_changes = path.pop
    while local_changes
      local_changes.each_pair {|key, change|
        if change.kind_of?(Array)
          pos[key] = (direction == :right) ? change[1] : change[0]
        else
          path.push([pos[key], change])
        end
      }
      pos, local_changes = path.pop
    end
    self
  end
  def apply_diff(changes, direction = :right)
    cloned = self.clone
    path = [[cloned, changes]]
    pos, local_changes = path.pop
    while local_changes
      local_changes.each_pair {|key, change|
        if change.kind_of?(Array)
          pos[key] = (direction == :right) ? change[1] : change[0]
        else
          pos[key] = pos[key].clone
          path.push([pos[key], change])
        end
      }
      pos, local_changes = path.pop
    end
    cloned
  end 
Run Code Online (Sandbox Code Playgroud)

所以要让左边看起来像你跑的那样

{a: {c: 1, b: 2}, b: 2}.apply_diff({:a=>{:c=>[1, 2]}, :b=>[2, nil]})
Run Code Online (Sandbox Code Playgroud)

要得到

{a: {c: 2, b: 2}, b: nil}
Run Code Online (Sandbox Code Playgroud)

为了得到准确,我们必须走得更远,记录nil和no key之间的差异,
通过提供添加和删除来缩短长数组也是很好的


Col*_*tin 20

编辑:

我一直回到这个代码,在我正在进行的项目中使用它.这是最新的,它对深层嵌套结构很有用,并且基于上面的Pete代码.我通常将它放在config/initializers/core_ext.rb中(在Rails项目中):

class Hash
  def deep_diff(other)
    (self.keys + other.keys).uniq.inject({}) do |memo, key|
      left = self[key]
      right = other[key]

      next memo if left == right

      if left.respond_to?(:deep_diff) && right.respond_to?(:deep_diff)
        memo[key] = left.deep_diff(right)
      else
        memo[key] = [left, right]
      end

      memo
    end
  end
end

class Array
  def deep_diff(array)
    largest = [self.count, array.count].max
    memo = {}

    0.upto(largest - 1) do |index|
      left = self[index]
      right = array[index]

      next if left == right

      if left.respond_to?(:deep_diff) && right.respond_to?(:deep_diff)
        memo[index] = left.deep_diff(right)
      else
        memo[index] = [left, right]
      end
    end

    memo
  end
end
Run Code Online (Sandbox Code Playgroud)

这是一个小型演示:

> {a: [{b: "c", d: "e"}, {b: "c", f: "g"}]}.deep_diff({a: [{b: "c", d: "e"}, {b: "d", f: "g"}]})
=> {:a=>{1=>{:b=>["c", "d"]}}}
Run Code Online (Sandbox Code Playgroud)

年龄较大的回应:

我发现Rails的Hash diff方法实际上并没有告诉我左侧和右侧是什么(这更有用).有一个插件调用"Riff",它已经消失,这将让你区分两个ActiveRecord对象.实质上:

class Hash
  def diff(other)
    self.keys.inject({}) do |memo, key|
      unless self[key] == other[key]
        memo[key] = [self[key], other[key]] 
      end
      memo
    end
  end
end
Run Code Online (Sandbox Code Playgroud)


gle*_*ald 11

如果你关心的只是element2中的独特之处,你可以这样做:

element2.to_a - element1.to_a
Run Code Online (Sandbox Code Playgroud)

  • 如果哈希包含其他哈希,似乎不起作用 (2认同)