在Ruby中创build一个数字,string,数组或散列的md5散列

我需要为Ruby中的variables创build一个签名string,其中variables可以是数字,string,散列或数组。 哈希值和数组元素也可以是这些types中的任何一种。

该string将用于比较数据库中的值(在本例中为Mongo)。

我的第一个想法是创build一个JSON编码值的MD5散列,如下所示:(body是上面提到的variables)

def createsig(body) Digest::MD5.hexdigest(JSON.generate(body)) end 

这几乎可以工作,但JSON.generate不会每次都按相同的顺序对一个散列的键进行编码,所以createsig({:a=>'a',:b=>'b'})并不总是等于createsig({:b=>'b',:a=>'a'})

创build签名string以适应此需求的最佳方法是什么?

注意:对于我们之间的细节,我知道你不能JSON.generate()一个数字或string。 在这些情况下,我会直接调用MD5.hexdigest()

我很快编写了以下代码,没有时间在工作中真正testing它,但它应该完成这项工作。 如果您发现任何问题,请告诉我,我会看看。

这应该适当地扁平化和sorting数组和散列,你需要有一些非常奇怪的string,因为有任何冲突。

 def createsig(body) Digest::MD5.hexdigest( sigflat body ) end def sigflat(body) if body.class == Hash arr = [] body.each do |key, value| arr << "#{sigflat key}=>#{sigflat value}" end body = arr end if body.class == Array str = '' body.map! do |value| sigflat value end.sort!.each do |value| str << value end end if body.class != String body = body.to_s << body.class.to_s end body end > sigflat({:a => {:b => 'b', :c => 'c'}, :d => 'd'}) == sigflat({:d => 'd', :a => {:c => 'c', :b => 'b'}}) => true 

如果只能得到body的string表示forms,而没有Ruby 1.8哈希函数以不同的顺序从一次到另一次返回,那么可以可靠地哈希该string表示forms。 让我们用一些猴子补丁来弄脏我们的手:

 require 'digest/md5' class Object def md5key to_s end end class Array def md5key map(&:md5key).join end end class Hash def md5key sort.map(&:md5key).join end end 

现在任何(在问题中提到的types的) md5key通过返回一个可靠的键来创build校验和来响应md5key ,所以:

 def createsig(o) Digest::MD5.hexdigest(o.md5key) end 

例:

 body = [ { 'bar' => [ 345, "baz", ], 'qux' => 7, }, "foo", 123, ] p body.md5key # => "bar345bazqux7foo123" p createsig(body) # => "3a92036374de88118faf19483fe2572e" 

注意:这个散列表示不会对结构进行编码,只能对这些值进行连接。 因此[“a”,“b”,“c”]将与[“abc”]相同的哈希值。

这是我的解决scheme。 我走的数据结构,并build立一个片段,join到一个单一的string列表。 为了确保所看到的类的types影响哈希,我注入了一个单一的Unicode字符,沿途编码基本types信息。 (例如,我们想要[“1”,“2”,“3”] objsum!= [1,2,3] .objsum)

我把它作为Object的一个改进,它很容易移植到一个猴子补丁。 要使用它只需要文件并运行“使用ObjSum”。

 module ObjSum refine Object do def objsum parts = [] queue = [self] while queue.size > 0 item = queue.shift if item.kind_of?(Hash) parts << "\\000" item.keys.sort.each do |k| queue << k queue << item[k] end elsif item.kind_of?(Set) parts << "\\001" item.to_a.sort.each { |i| queue << i } elsif item.kind_of?(Enumerable) parts << "\\002" item.each { |i| queue << i } elsif item.kind_of?(Fixnum) parts << "\\003" parts << item.to_s elsif item.kind_of?(Float) parts << "\\004" parts << item.to_s else parts << item.to_s end end Digest::MD5.hexdigest(parts.join) end end end 

只是我2美分:

 module Ext module Hash module InstanceMethods # Return a string suitable for generating content signature. # Signature image does not depend on order of keys. # # {:a => 1, :b => 2}.signature_image == {:b => 2, :a => 1}.signature_image # => true # {{:a => 1, :b => 2} => 3}.signature_image == {{:b => 2, :a => 1} => 3}.signature_image # => true # etc. # # NOTE: Signature images of identical content generated under different versions of Ruby are NOT GUARANTEED to be identical. def signature_image # Store normalized key-value pairs here. ar = [] each do |k, v| ar << [ k.is_a?(::Hash) ? k.signature_image : [k.class.to_s, k.inspect].join(":"), v.is_a?(::Hash) ? v.signature_image : [v.class.to_s, v.inspect].join(":"), ] end ar.sort.inspect end end end end class Hash #:nodoc: include Ext::Hash::InstanceMethods end 

根据你的需要,你甚至可以打电话给ary.inspectary.to_yaml

Interesting Posts