Ruby:合并嵌套哈希

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9381553/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 04:48:07  来源:igfitidea点击:

Ruby: merge nested hash

rubyhashmergenested

提问by user1223862

I would like to merge a nested hash.

我想合并一个嵌套的哈希。

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

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

I would like the merge to be:

我希望合并是:

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

What is the nest way to accomplish this?

实现此目的的嵌套方法是什么?

回答by xlembouras

For rails 3.0.0+ or higher version there is the deep_mergefunction for ActiveSupportthat does exactly what you ask for.

对于轨道3.0.0+或更高版本存在deep_merge的功能的ActiveSupport那不正是你问什么。

回答by Jon M

I found a more generic deep-merge algorithm here, and used it like so:

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

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)

回答by Dan

To add on to Jon M and koendc's answers, the below code will handle merges of hashes, and :nil as above, but it will also union any arrays that are present in both hashes (with the same key):

要添加到 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)

回答by Russell

For variety's sake - and this will only work if you want to merge all the keys in your hash in the same way - you could do this:

出于多样性的考虑 - 只有当您想以相同的方式合并哈希中的所有键时,这才有效 - 您可以这样做:

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

When you pass a block to Hash#merge, kis the key being merged, where the key exists in both aand b, xis the value of a[k]and yis the value of b[k]. The result of the block becomes the value in the merged hash for key k.

当你传递一个块Hash#mergek关键是要合并,其中关键在两个存在abx是价值a[k]y为价值b[k]。块的结果成为 key 的合并散列中的值k

I think in your specific case though, nkm's answer is better.

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

回答by Steve Midgley

A little late to answer your question, but I wrote a fairly rich deep merge utility awhile back that is now maintained by Daniel Deleo on Github: https://github.com/danielsdeleo/deep_merge

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

It will merge your arrays exactly as you want. From the first example in the docs:

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

So if you have two hashes like this:

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

   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}

It won't merge :y (because int and array aren't considered mergeable) - using the bang (!) syntax causes the source to overwrite.. Using the non-bang method will leave dest's internal values alone when an unmergeable entity is found. It will add the arrays contained in :x together because it knows how to merge arrays. It handles arbitrarily deep merging of hashes containing whatever data structures.

它不会合并 :y (因为 int 和 array 不被认为是可合并的) - 使用 bang (!) 语法会导致源覆盖.. 当不可合并的实体是成立。它将把包含在 :x 中的数组加在一起,因为它知道如何合并数组。它处理包含任何数据结构的散列的任意深度合并。

Lots more docs on Daniel's github repo now..

Daniel 的 github repo 现在有更多文档..

回答by akostadinov

All answers look to me overcomplicated. Here's what I came up with eventually:

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

# @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

P.S. use as public, WTFPL or whatever license

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

回答by MOPO3OB

Here is even better solution for recursive mergingthat uses refinementsand has bang methodalongside with block support. This code does work on pureRuby.

这是递归合并的更好解决方案,它使用细化并具有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

After using HashRecursivewas executed you can use default Hash::mergeand Hash::merge!as if they haven't been modified. You can use blockswith these methods as before.

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

The new thing is that you can pass boolean recursive(second argument) to these modified methods and they will merge hashes recursively.

新的东西是你可以将布尔值recursive(第二个参数)传递给这些修改过的方法,它们将递归地合并散列。



Examplefor simple usage is written at this answer. Here is an advanced example.

简单用法示例写在这个答案中。这是一个高级示例。

The example in this question is bad because it got nothing to do with recursive merging. Following line would meet question's example:

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

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

Let me give you a better example to show the power of the code above. Imagine two rooms, each have one bookshelf in it. There are 3 rows on each bookshelf and each bookshelf currently have 2 books. Code:

让我给你一个更好的例子来展示上面代码的威力。想象一下两个房间,每个房间都有一个书架。每个书架上有 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"
            }
        ]
    }
}

We are going to move books from the shelf in the second room to the same rows on the shelf in the first room. First we will do this without setting recursiveflag, i.e. same as using unmodified Hash::merge!:

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

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

The output will tell us that the shelf in the first room would look like this:

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

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

As you can see, not having recursiveforced us to throw out our precious books.

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

Now we will do the same thing but with setting recursiveflag to true. You can pass as second argument either recursive=trueor just true:

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

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

Now the output will tell us that we actually moved our books:

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

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"
            }
        ]
    }
}

That last execution could be rewritten as following:

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

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

or

或者

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

That's it. Also take a look at my recursive version of Hash::each(Hash::each_pair) here.

就是这样。还可以在此处查看我的Hash::each( Hash::each_pair)递归版本。

回答by koendc

I think Jon M's answer is the best, but it fails when you merge in a hash with a nil/undefined value. This update solves the issue:

我认为 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)

回答by nkm

a[:book] = a[:book] + b[:book]

Or

或者

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