我想从ruby哈希计算一个唯一的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
修改
我通过以下答案得出的另一个问题是这两个哈希:
a = {a: "a", b: "b"}
b = {a: "b", b: "a"}
当展平哈希并对其进行排序时,即使a == b => false
,这两个哈希值也会产生相同的输出。
编辑2
这整件事的用例是产品数据比较。产品数据存储在哈希中,然后序列化并发送到创建/更新产品数据的服务。
我想检查产品数据中是否有任何更改,因此我从产品内容生成哈希并将其存储在数据库中。下次加载相同的产品时,我再次计算哈希值,将其与数据库中的哈希值进行比较,并确定产品是否需要更新。
答案 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)
您还没有明确说明在散列之前更改数据结构的有用理由。因此,除非您的数据结构包含不受支持的对象(如绑定或触发器),否则您应该考虑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"