【问题标题】:How to compare two multidimensional hashes regarding to elements order如何比较关于元素顺序的两个多维哈希
【发布时间】:2020-03-13 23:04:29
【问题描述】:

我陷入了这样的问题。我有 2 个哈希值,我试图在 rspec 测试中进行比较:

describe 'sort tests' do
  let(:actual) do
    {
      1 => { users: { 1 => { id: 1,
                             accounts: [1],
                             profit: 10,
                             revenue: 1,
                             loss: 9 },
                      2 => { id: 2,
                             accounts: [2, 3, 6],
                             profit: -24,
                             revenue: 6,
                             loss: -30 } },
             total_profit: -14,
             total_revenue: 7,
             total_loss: -21 },
      2 => { users: { 3 => { id: 3,
                             accounts: [4, 5],
                             profit: 27,
                             revenue: 9,
                             loss: 18 } },
             total_profit: 27,
             total_revenue: 9,
             total_loss: 18 }
    }
  end
  let(:expected) do
    {
      1 => { users: { 2 => { id: 2,
                             accounts: [2, 3, 6],
                             profit: -24,
                             revenue: 6,
                             loss: -30 },
                      1 => { id: 1,
                             accounts: [1],
                             profit: 10,
                             revenue: 1,
                             loss: 9 } },
             total_profit: -14,
             total_revenue: 7,
             total_loss: -21 },
      2 => { users: { 3 => { id: 3,
                             accounts: [4, 5],
                             profit: 27,
                             revenue: 9,
                             loss: 18 } },
             total_profit: 27,
             total_revenue: 9,
             total_loss: 18 }
    }
  end

  it 'sort' do
    def mysort(data)
      data.each do |_, partner|
        partner[:users].sort_by { |_k, user| user[:loss] }.to_h
        partner
      end
      data.sort_by { |_, partner| partner[:total_loss] }.to_h
    end
    expect(mysort(actual)).to eql expected
    # expect(Digest::MD5.hexdigest(Marshal::dump(mysort(actual)))).to eql Digest::MD5.hexdigest(Marshal::dump(expected))
  end
end

测试通过了。但是,如果我取消注释 md5 检查,它将引发哈希不同的错误:

expected: "155d27d209f286fb1fc9ebeb0dcd6d3d"
     got: "255df98d4fc8166d0d8ffc7227ffd351"

所以,eql 实际上并没有正确比较哈希值,并且 mysort 函数中存在错误:

def mysort(data)
  data.each do |_, partner|
    partner[:users] = partner[:users].sort_by { |_k, user| user[:loss] }.to_h
    partner
  end
  data.sort_by { |_, partner| partner[:total_loss] }.to_h
end

现在排序正常,但仅比较 md5 校验和有助于了解哈希是否相等:(

如何在没有这个 hack 的情况下比较哈希?

【问题讨论】:

  • "eql 实际上没有正确比较哈希",这取决于您要如何比较。对于大多数情况,这是首选行为。因为键值对的顺序在大多数情况下并不重要。 documentation 声明 “相等——如果两个散列都包含相同数量的键,并且每个键值对等于(根据 Object#==)另一个散列中的相应元素,则它们是相等的。” 但是通过问题猜测您还想比较键的顺序?
  • 是的。我也想比较一下哈希的顺序。

标签: ruby multidimensional-array hash rspec


【解决方案1】:

我会使用这样的东西:

compare = proc do |a, b|
  next a == b unless a.is_a?(Hash) && b.is_a?(Hash)
  next false  unless a.size == b.size

  a.keys.zip(b.keys).all?(compare) && a.values.zip(b.values).all?(compare)
end

在上述情况下,您不能将proc 换成lambda。原因是 proc 隐式地进行数组分解而 lambda 没有。 (你可以使用 compare.([a, b]) 而不破坏它,而使用 lambda 时你不能这样做。)

我个人是保护子句的粉丝,主要是因为我发现它们比一个大表达式更清晰。

此解决方案检查顺序和值的顺序。

compare.({{a: 1, b: 2} => 1}, {{b: 2, a: 1} => 1}) #=> false
compare.({a: {b: 2, c: 3}}, {a: {c: 3, b: 2}})     #=> false
compare.({a: {b: 2, c: 3}}, {a: {b: 2, c: 3}})     #=> true

ps。如果您使用的是 2.5.0 以下的 Ruby 版本,则必须改用 .all?(&compare)(注意 &)。

【讨论】:

  • 一旦您已经使用了提前退货,那么break false 而不是next false确实 提前退货是有意义的。
  • @AlekseiMatiushkin 是的,但我将迭代留给all? 方法,该方法应在达到第一个虚假值时停止迭代。
  • 我怀疑我跟不上了。如果a.size != b.size,你不会留下任何东西。如果这发生在第一个键上,而哈希有 1B 个键,其他一切都相等,break 将立即返回,而您的 next 将迭代它 1B 次。我不写 ruby​​,坦率地说,我不能说我喜欢 ruby​​,但 break 是有史以来最好的功能之一。
  • @AlekseiMatiushkin 我想说的是all? 负责迭代,因此我使用next 而不是breakall? 的工作是在第一个虚假值被命中后立即返回。这可以在这里看到:[1, *1..10].zip(0..10).all? { |a, b| p [a, b]; a == b },其中只打印了[1, 0],这意味着当一个假值被命中时它不会迭代整个集合。
  • 再一次。假设我们有 2 个散列,每个散列都有十亿个键。第一个键有两个不同大小的子键。 break 的版本需要 1 次迭代,next 将迭代整个十亿。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-03
  • 2017-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多