【问题标题】:Flatten deep nested hash to array for sha1 hashing将深度嵌套散列展平为数组以进行 sha1 散列
【发布时间】:2016-08-10 06:57:59
【问题描述】:

我想从一个 ruby​​ 哈希计算一个唯一的 sha1 哈希。我想过

  • (深度)将哈希转换为数组
  • 对数组进行排序
  • 空字符串加入数组
  • 计算sha1

考虑以下哈希:

hash = {
  foo: "test",
  bar: [1,2,3]
  hello: {
    world: "world",
    arrays: [
      {foo: "bar"}
    ]
  }
}

我怎样才能将这种嵌套散列放入一个数组中

[:foo, "test", :bar, 1, 2, 3, :hello, :world, "earth", :arrays, :my, "example"]

然后我会对数组进行排序,将其与array.join("") 连接起来,然后像这样计算 sha1 哈希:

require 'digest/sha1'
Digest::SHA1.hexdigest hash_string
  1. 如何像上面描述的那样展平哈希?
  2. 是否已经有这方面的宝石?
  3. 有没有更快/更简单的方法来解决这个问题?我有大量的对象要转换(~700k),所以性能很重要。

编辑

我通过下面的答案发现的另一个问题是这两个哈希:

a = {a: "a", b: "b"}
b = {a: "b", b: "a"}

在展平散列并对其进行排序时,这两个散列会产生相同的输出,即使在 a == b => false 时也是如此。

编辑 2

整个事情的用例是产品数据比较。产品数据存储在哈希中,然后序列化并发送到创建/更新产品数据的服务。

我想检查产品数据中是否有任何更改,因此我从产品内容生成哈希并将其存储在数据库中。下次加载相同的产品时,我会再次计算哈希值,将其与数据库中的哈希值进行比较,然后确定产品是否需要更新。

【问题讨论】:

  • 这是一个 X/Y 问题。虽然 MRI Ruby 哈希是有序的,但您不能保证哈希排序。您必须比较特定的键/值对,或者依赖序列化顺序(可能在排序之后)。您可能需要重新考虑数据的表示。
  • 请在bar: [1,2,3]后加逗号。我不明白为什么有人给出答案没有提到这个遗漏。
  • 您的哈希与您希望从中生成的数组不匹配。同样,为什么那些给出答案的人没有提到这一点? “马虎”是我用来描述这个问题的词。

标签: ruby hash sha1 sha


【解决方案1】:

编辑:正如您所详述的,具有不同顺序键的两个散列应该给出相同的字符串。我将重新打开 Hash 类以添加我的新自定义 flatten 方法:

class Hash
  def custom_flatten()
    self.sort.map{|pair| ["key: #{pair[0]}", pair[1]]}.flatten.map{ |elem| elem.is_a?(Hash) ? elem.custom_flatten : elem }.flatten
  end
end

解释:

  • sort 将哈希转换为对的排序数组(用于比较具有不同键顺序的哈希)
  • .map{|pair| ["key: #{pair[0]}", pair[1]]} 是一种将键与最终展平数组中的值区分开来的技巧,以避免出现{a: {b: {c: :d}}}.custom_flatten == {a: :b, c: :d}.custom_flatten 的问题
  • flatten 将数组数组转换为单个值数组
  • map{ |elem| elem.is_a?(Hash) ? elem.custom_flatten : elem } 在剩余的任何子哈希上回调 fully_flatten

那么你只需要使用:

require 'digest/sha1'
Digest::SHA1.hexdigest hash.custom_flatten.to_s

【讨论】:

  • 这种方法的问题是,哈希 a = {foo: "foo", bar: "bar"} 创建 "{:foo=>\"foo\", :bar=>\"bar\"}"b = {bar: "bar", foo: "foo"} 创建不同的表示 "{:bar=>\"bar\", :foo=>\"foo\"}",尽管 a == b => true
  • 对。我将编辑我的答案以尝试解决您的比较问题。
  • 这是你需要的Hash#fully_flatten()函数吗?
  • 这看起来很有希望,谢谢!我会用不同的哈希来测试一下
  • 如果您出于某些原因不想让此方法可用于您的所有项目,则可以使 Hash 的此新方法仅在具有 class_eval 的特定类的范围内可用。
【解决方案2】:

我不知道有什么宝石可以做您正在寻找的事情。 ruby 中有一个Hash#flatten 方法,但它不会递归地展平嵌套哈希。这是一个简单的递归函数,它将按照您在问题中要求的方式展平:

def completely_flatten(hsh)
  hsh.flatten(-1).map{|el| el.is_a?(Hash) ? completely_flatten(el) : el}.flatten
end

这将产生

hash = {
  foo: "test",
  bar: [1,2,3]
  hello: {
    world: "earth",
    arrays: [
      {my: "example"}
    ]
  }
}

completely_flatten(hash) 
#=> [:foo, "test", :bar, 1, 2, 3, :hello, :world, "earth", :arrays, :my, "example"]

要获得您正在寻找的字符串表示形式(在进行 sha1 哈希之前),在排序之前将数组中的所有内容转换为字符串,以便可以有意义地比较所有元素,否则您将收到错误:

hash_string = completely_flatten(hash).map(&:to_s).sort.join
#=> "123arraysbarearthexamplefoohellomytestworld"

【讨论】:

  • 感谢您的回答。正如我在下面评论的那样,这也不能解决 {a: "a", b: "b"}{a: "b", b: "a"} 两个哈希的问题。我已经更新了我的问题
  • @23tux 您能否就您的用例提供更多信息?你到底是如何使用 sha1 哈希的?您要验证什么?
  • 我已将我的用例添加到我的问题中
【解决方案3】:

使用 Marshal 进行快速序列化

您没有阐明在散列之前更改数据结构的有用理由。因此,您应该考虑marshaling 以提高速度,除非您的数据结构包含不受支持的对象,例如绑定或过程。例如,使用语法更正的 hash 变量:

require 'digest/sha1'

hash = {
  foo: "test",
  bar: [1,2,3],
  hello: {
    world: "world",
    arrays: [
      {foo: "bar"}
    ]
  }
}
Digest::SHA1.hexdigest Marshal.dump(hash)
#=> "f50bc3ceb514ae074a5ab9672ae5081251ae00ca"

Marshal 通常比其他序列化选项更快。如果您只需要速度,那将是您最好的选择。但是,由于其他原因,您可能会发现 JSON、YAML 或简单的 #to_s 或 #inspect 更能满足您的需求。只要您比较对象的相似表示,散列对象的内部格式在很大程度上与确保您拥有唯一或未修改的对象无关。

【讨论】:

  • 这种方法也有问题,这两个哈希{a: "a", b: "b"}{b: "b", a: "a"} 产生不同的结果,即使a == b => true
  • @23tux 这是一个特性,而不是一个错误。虽然这两个对象可能具有对象级别的相等性,但序列化的对象不同的。这就是为什么您需要比较序列化对象而不是无序对象的原因。如果您故意尝试散列无序对象以在对象级别进行比较,那么整个问题可能是您在 OP 中没有很好地表达的 X/Y 问题。
【解决方案4】:

任何基于展平散列的解决方案都将因嵌套散列而失败。一个健壮的解决方案是显式地对每个散列的键进行递归排序(从 ruby​​ 1.9.x 开始,散列键顺序被保留),然后将其序列化为字符串并对其进行消化。

  def canonize_hash(h)
    r = h.map { |k, v| [k, v.is_a?(Hash) ? canonize_hash(v) : v] }
    Hash[r.sort]
  end

  def digest_hash(hash)
    Digest::SHA1.hexdigest canonize_hash(hash).to_s
  end

  digest_hash({ foo: "foo", bar: "bar" })
  # => "ea1154f35b34c518fda993e8bb0fe4dbb54ae74a"
  digest_hash({ bar: "bar", foo: "foo" })
  # => "ea1154f35b34c518fda993e8bb0fe4dbb54ae74a"

【讨论】:

    【解决方案5】:

    问题是如何“展平”散列。还有一个关于sha1 的隐含问题,但根据 SO 规则,需要在单独的问题中解决。您可以按如下方式“展平”任何散列或数​​组。

    代码

    def crush(obj)
      recurse(obj).flatten
    end
    
    def recurse(obj)
      case obj
      when Array then obj.map { |e| recurse e }
      when Hash  then obj.map { |k,v| [k, recurse(v)] }
      else obj
      end
    end
    

    示例

    crush({
      foo: "test",
      bar: [1,2,3],
      hello: {
        world: "earth",
        arrays: [{my: "example"}]
      }
    })
      #=> [:foo, "test", :bar, 1, 2, 3, :hello, :world, "earth", :arrays, :my, "example"]
    
    crush([[{ a:1, b:2 }, "cat", [3,4]], "dog", { c: [5,6] }])
      #=> [:a, 1, :b, 2, "cat", 3, 4, "dog", :c, 5, 6]
    

    【讨论】:

      猜你喜欢
      • 2014-06-24
      • 2012-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-07
      • 2018-12-28
      • 2013-03-14
      • 2018-11-07
      相关资源
      最近更新 更多