Ruby 将深度嵌套哈希展平到数组以进行sha1哈希

Ruby 将深度嵌套哈希展平到数组以进行sha1哈希,ruby,hash,sha1,sha,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, :wor

我想从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
  • 我怎样才能像上面描述的那样将散列变平
  • 这个已经有宝石了吗
  • 有没有更快/更简单的方法来解决这个问题?我有大量的对象要转换(~700k),所以性能很重要
  • 编辑

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

    a = {a: "a", b: "b"}
    b = {a: "b", b: "a"}
    
    在展平散列并对其排序时,这两个散列产生相同的输出,即使
    a==b=>false

    编辑2

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


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

    编辑:正如您所述,两个具有不同顺序键的哈希值应给出相同的字符串。我将重新打开哈希类以添加新的自定义展平方法:

    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{124; pair}[“key:{pair[0]}”,pair[1]}
      是一种将键与最终展平数组中的值区分开来的技巧,以避免
      {a:{b:{c::d}}的问题。自定义展平=={a::b,c::d}。自定义展平
    • 展平
      将数组转换为单个值数组
    • map{elem | elem.is_a?(散列)?elem.custom_flatten:elem}
      在任何剩余子散列上回调
      fully_flatte
    那么您只需要使用:

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

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

    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{124; pair}[“key:{pair[0]}”,pair[1]}
      是一种将键与最终展平数组中的值区分开来的技巧,以避免
      {a:{b:{c::d}}的问题。自定义展平=={a::b,c::d}。自定义展平
    • 展平
      将数组转换为单个值数组
    • map{elem | elem.is_a?(散列)?elem.custom_flatten:elem}
      在任何剩余子散列上回调
      fully_flatte
    那么您只需要使用:

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

    我不知道有什么宝石能像你要找的那样。ruby中有一个方法,但它不会递归地展平嵌套哈希。下面是一个直接的递归函数,它将按照您在问题中要求的方式展开:

    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"
    

    我不知道有什么宝石能像你要找的那样。ruby中有一个方法,但它不会递归地展平嵌套哈希。下面是一个直接的递归函数,它将按照您在问题中要求的方式展开:

    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"
    
    使用封送处理进行快速序列化 在散列之前,您还没有阐明更改数据结构的有用理由。因此,除非数据结构包含不支持的对象(如绑定或PROS),否则应考虑速度。例如,使用已更正语法的哈希变量:

    require 'digest/sha1'
    
    hash = {
      foo: "test",
      bar: [1,2,3],
      hello: {
        world: "world",
        arrays: [
          {foo: "bar"}
        ]
      }
    }
    Digest::SHA1.hexdigest Marshal.dump(hash)
    #=> "f50bc3ceb514ae074a5ab9672ae5081251ae00ca"
    
    封送处理通常比其他序列化选项更快。如果你只需要速度,那将是你最好的选择。然而,由于其他原因,您可能会发现JSON、YAML或简单的“to#s”或“inspect”更能满足您的需求。只要比较对象的类似表示,哈希对象的内部格式与确保对象唯一或未修改基本无关。

    使用封送处理进行快速序列化 在散列之前,您还没有阐明更改数据结构的有用理由。因此,除非数据结构包含不支持的对象(如绑定或PROS),否则应考虑速度。例如,使用已更正语法的哈希变量:

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

    封送处理通常比其他序列化选项更快。如果你只需要速度,那将是你最好的选择。然而,由于其他原因,您可能会发现JSON、YAML或简单的“to#s”或“inspect”更能满足您的需求。只要比较对象的类似表示形式,哈希对象的内部格式与确保对象唯一或未修改基本无关。

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

    任何基于展平散列w的解决方案