【问题标题】:Ruby: merge nested hashRuby:合并嵌套哈希
【发布时间】:2019-02-21 00:36:39
【问题描述】:

我想合并一个嵌套哈希。

a = {:book=>
    [{:title=>"Hamlet",
      :author=>"William Shakespeare"
      }]}

b = {:book=>
    [{:title=>"Pride and Prejudice",
      :author=>"Jane Austen"
      }]}

我希望合并为:

{:book=>
   [{:title=>"Hamlet",
      :author=>"William Shakespeare"},
    {:title=>"Pride and Prejudice",
      :author=>"Jane Austen"}]}

实现这一点的嵌套方式是什么?

【问题讨论】:

标签: ruby hash merge nested


【解决方案1】:
a[:book] = a[:book] + b[:book]

或者

a[:book] <<  b[:book].first

【讨论】:

  • 这适用于这种特殊情况,但考虑到这个问题的标题及其在搜索结果中的位置,我认为我们需要一个通用的递归合并解决方案。
【解决方案2】:

为了多样化 - 只有当你想以相同的方式合并哈希中的所有键时,这才有效 - 你可以这样做:

a.merge(b) { |k, x, y| x + y }

当你将一个block传递给Hash#merge时,k是被合并的key,其中key同时存在于ab中,xa[k]y的值是b[k] 的值。块的结果成为键 k 的合并哈希值。

我认为在你的具体情况下,nkm 的答案更好。

【讨论】:

  • NoMethodError: {:color=>"red"}:Hash 的未定义方法 `+'
  • 您似乎正在尝试将此答案与包含其他键的哈希一起使用 - {:color=&gt;"red"} 不在您的示例中。正如我在回答中所说,这仅在您想以相同方式合并哈希中的所有键时才有效。
  • 也许您可以将您正在使用的哈希完整添加到问题中?
  • 这实际上是一个非常方便的技巧!不知道Hash#merge 选择了一个可选块。
  • 如果将键的散列合并到数组中,您可以执行以下操作来合并空列表:a.merge(b) { |k, x, y| x + (y ? y : []) }
【解决方案3】:

我找到了一个更通用的深度合并算法here,并像这样使用它:

class ::Hash
    def deep_merge(second)
        merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
        self.merge(second, &merger)
    end
end

a.deep_merge(b)

【讨论】:

  • 注意:对于散列中的数组,它只是覆盖现有的数组值。 S. 由@Dan 回答,以获得更复杂的数组处理。
【解决方案4】:

对于 rails 3.0.0+ 或更高版本,ActiveSupportdeep_merge 函数完全符合您的要求。

【讨论】:

  • 这不起作用。它将用新数组替换现有数组。 apidock.com/rails/v3.2.13/Hash/deep_merge 它似乎适用于 Rails 4:apidock.com/rails/v4.0.2/Hash/deep_merge
  • 没有。虽然 Rails 3.2.18 确实有 deep_merge 方法,但它只接受来自 4.0.2 版本的块
  • @mirelon OP 没有要求阻止,所以答案是正确的。
  • 但是,问题不在于 Rails,是吗?因此,这个答案是获得最多支持的答案,这有点令人恼火。
【解决方案5】:

回答你的问题有点晚了,但我不久前写了一个相当丰富的深度合并实用程序,现在由 Daniel Deleo 在 Github 上维护:https://github.com/danielsdeleo/deep_merge

它将完全按照您的需要合并您的数组。从文档中的第一个示例:

所以如果你有两个这样的哈希:

   source = {:x => [1,2,3], :y => 2}
   dest =   {:x => [4,5,'6'], :y => [7,8,9]}
   dest.deep_merge!(source)
   Results: {:x => [1,2,3,4,5,'6'], :y => 2}

它不会合并 :y (因为 int 和数组不被认为是可合并的) - 使用 bang (!) 语法会导致源覆盖。使用 non-bang 方法将在找到不可合并的实体。它将 :x 中包含的数组添加在一起,因为它知道如何合并数组。它处理包含任何数据结构的哈希的任意深度合并。

现在在 Daniel 的 github 存储库上有更多文档..

【讨论】:

    【解决方案6】:

    我认为 Jon M 的答案是最好的,但是当您合并具有 nil/undefined 值的哈希时它会失败。 本次更新解决了这个问题:

    class ::Hash
        def deep_merge(second)
            merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
            self.merge(second, &merger)
        end
    end
    
    a.deep_merge(b)
    

    【讨论】:

    • 我没有看到 nil 值的问题。举一个有问题的例子。
    • nil.deep_merge({'one'=>'two'}) 为 'nil' 引发 'no method 'deep_merge'。
    【解决方案7】:

    在我看来,所有答案都过于复杂。这是我最终想出的:

    # @param tgt [Hash] target hash that we will be **altering**
    # @param src [Hash] read from this source hash
    # @return the modified target hash
    # @note this one does not merge Arrays
    def self.deep_merge!(tgt_hash, src_hash)
      tgt_hash.merge!(src_hash) { |key, oldval, newval|
        if oldval.kind_of?(Hash) && newval.kind_of?(Hash)
          deep_merge!(oldval, newval)
        else
          newval
        end
      }
    end
    

    附:用作公共、WTFPL 或任何许可证

    【讨论】:

      【解决方案8】:

      为了补充 Jon M 和 koendc 的答案,下面的代码将处理哈希的合并,和上面的 :nil 一样,但它也会合并两个哈希中存在的任何数组(具有相同的键):

      class ::Hash
        def deep_merge(second)
          merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
          merge(second.to_h, &merger)
        end
      end
      
      
      a.deep_merge(b)
      

      【讨论】:

        【解决方案9】:

        这里是更好的递归合并解决方案,它使用refinements并具有bang方法以及块支持 .此代码确实适用于 Ruby。

        module HashRecursive
            refine Hash do
                def merge(other_hash, recursive=false, &block)
                    if recursive
                        block_actual = Proc.new {|key, oldval, newval|
                            newval = block.call(key, oldval, newval) if block_given?
                            [oldval, newval].all? {|v| v.is_a?(Hash)} ? oldval.merge(newval, &block_actual) : newval
                        }   
                        self.merge(other_hash, &block_actual)
                    else
                        super(other_hash, &block)
                    end
                end
                def merge!(other_hash, recursive=false, &block)
                    if recursive
                        self.replace(self.merge(other_hash, recursive, &block))
                    else
                        super(other_hash, &block)
                    end
                end
            end
        end
        
        using HashRecursive
        

        using HashRecursive 执行后,您可以使用默认的Hash::mergeHash::merge!,就好像它们没有被修改一样。您可以像以前一样在这些方法中使用blocks

        新功能是您可以将布尔值recursive(第二个参数)传递给这些修改后的方法,它们将递归地合并哈希。


        示例写在this answer。这是一个高级示例。

        这个问题中的示例很糟糕,因为它与递归合并无关。以下行将满足问题的示例:

        a.merge!(b) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
        

        让我给你一个更好的例子来展示上面代码的威力。想象一下两个房间,每个房间里都有一个书架。每个书架有 3 排,每个书架目前有 2 本书。代码:

        room1   =   {
            :shelf  =>  {
                :row1   =>  [
                    {
                        :title  =>  "Hamlet",
                        :author =>  "William Shakespeare"
                    }
                ],
                :row2   =>  [
                    {
                        :title  =>  "Pride and Prejudice",
                        :author =>  "Jane Austen"
                    }
                ]
            }
        }
        
        room2   =   {
            :shelf  =>  {
                :row2   =>  [
                    {
                        :title  =>  "The Great Gatsby",
                        :author =>  "F. Scott Fitzgerald"
                    }
                ],
                :row3   =>  [
                    {
                        :title  =>  "Catastrophe Theory",
                        :author =>  "V. I. Arnol'd"
                    }
                ]
            }
        }
        

        我们要将书从第二个房间的书架上移到第一个房间书架上的同一行。首先,我们将在不设置recursive 标志的情况下执行此操作,即与使用未修改的Hash::merge! 相同:

        room1.merge!(room2) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
        puts room1
        

        输出会告诉我们第一个房间的架子看起来像这样:

        room1   =   {
            :shelf  =>  {
                :row2   =>  [
                    {
                        :title  =>  "The Great Gatsby",
                        :author =>  "F. Scott Fitzgerald"
                    }
                ],
                :row3   =>  [
                    {
                        :title  =>  "Catastrophe Theory",
                        :author =>  "V. I. Arnol'd"
                    }
                ]
            }
        }
        

        如您所见,没有recursive 迫使我们扔掉我们珍贵的书籍。

        现在我们将做同样的事情,但将recursive 标志设置为true。您可以将 recursive=truetrue 作为第二个参数传递:

        room1.merge!(room2, true) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
        puts room1
        

        现在输出会告诉我们,我们实际上移动了我们的书:

        room1   =   {
            :shelf  =>  {
                :row1   =>  [
                    {
                        :title  =>  "Hamlet",
                        :author =>  "William Shakespeare"
                    }
                ],
                :row2   =>  [
                    {
                        :title  =>  "Pride and Prejudice",
                        :author =>  "Jane Austen"
                    },
                    {
                        :title  =>  "The Great Gatsby",
                        :author =>  "F. Scott Fitzgerald"
                    }
                ],
                :row3   =>  [
                    {
                        :title  =>  "Catastrophe Theory",
                        :author =>  "V. I. Arnol'd"
                    }
                ]
            }
        }
        

        最后一次执行可以改写如下:

        room1 = room1.merge(room2, recursive=true) do |k, v1, v2|
            if v1.is_a?(Array) && v2.is_a?(Array)
                v1+v2
            else
                v2
            end
        end
        puts room1
        

        block = Proc.new {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
        room1.merge!(room2, recursive=true, &block)
        puts room1
        

        就是这样。也看看我的递归版本Hash::each(Hash::each_pair)here

        【讨论】:

          猜你喜欢
          • 2019-08-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-16
          • 2019-11-21
          • 2015-11-22
          • 1970-01-01
          相关资源
          最近更新 更多