将深层嵌套哈希展平为数组以进行sha1哈希

时间:2016-04-18 13:39:00

标签: ruby hash sha1 sha

我想从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),所以性能很重要。
  4. 修改

    我通过以下答案得出的另一个问题是这两个哈希:

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

    当展平哈希并对其进行排序时,即使a == b => false,这两个哈希值也会产生相同的输出。

    编辑2

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

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

5 个答案:

答案 0 :(得分:2)

编辑:正如您详细说明的那样,两个带有不同顺序键的哈希应该给出相同的字符串。我会重新打开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

答案 1 :(得分:1)

我不知道有什么东西像你在找什么。 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"

答案 2 :(得分:1)

问题是如何平整"哈希。关于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]

答案 3 :(得分:0)

使用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因其他原因更好地满足您的需求。只要您比较对象的类似表示,散列对象的内部格式与确保您拥有唯一或未修改的对象无关。

答案 4 :(得分:0)

任何基于展平哈希的解决方案都会因嵌套哈希而失败。一个强大的解决方案是以递归方式显式地对每个哈希的键进行排序(从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"