如何在Ruby中合并多个哈希表?

41
h  = { a: 1 }
h2 = { b: 2 }
h3 = { c: 3 }

Hash#merge适用于2个哈希表:h.merge(h2)

如何合并3个哈希表?

h.merge(h2).merge(h3)可以实现,但是否有更好的方法?


4
从 Ruby 2.6 开始,merge 函数可以接收多个哈希作为参数。更多信息请参见这里 - SRack
11个回答

56

你可以像这样做:

h, h2, h3  = { a: 1 }, { b: 2 }, { c: 3 }
a  = [h, h2, h3]

p Hash[*a.map(&:to_a).flatten] #= > {:a=>1, :b=>2, :c=>3}

编辑:如果你有很多哈希值,这可能是正确的做法:

a.inject{|tot, new| tot.merge(new)}
# or just
a.inject(&:merge)

这对我来说“原样”可以工作,但当它更像是h1、h2、h3 = { a: 1, b: 4}, { b: 2, c: 5}, {c: 3, a: 6}时就不太行了。 - Some Guy
抱歉,我回答得太快了,请看我的更新。在你的例子中,你有两次使用键:a,其中一个将被覆盖,因为哈希只能有不同的键。 - hirolau
6
请注意,array.reduce(&:merge)将会得到与inject相同的结果(inject只是reduce的别名)。 - ocodo
有人可以详细解释一下第一个解决方案吗?我不明白 p "Hash[*a.map(&:to_a).flatten]" 的作用是什么。 - zealouscoder
@ZealousCoder 我来晚了,但是想给你一个答案。Hash[]会接受一系列的参数并将它们配对构建成哈希表。因此,如果你通过a.map(&:to_a).flatten从哈希表中生成一个有序数组,并使用展开运算符将它们作为参数传递,那么它将很好地工作。然而,Hash[]也接受一个由k-v对表示的2元数组的数组,我会将其写成Hash[*a.flat_map(&:to_a)] - Brennan

23

Ruby 2.0开始,这可以更加优雅地完成:

h.merge **h1, **h2

在出现重叠键的情况下,后面的键会优先生效:

h  = {}
h1 = { a: 1, b: 2 }
h2 = { a: 0, c: 3 }

h.merge **h1, **h2
# => {:a=>0, :b=>2, :c=>3}

h.merge **h2, **h1
# => {:a=>1, :c=>3, :b=>2}

1
** 是什么意思? - Arnold Roa
1
@ArnoldRoa - 它会解包哈希,类似于数组的单个扩展运算符('*')。 - bwv549
1
谢谢@jayqui,这是一个非常重要的“陷阱”。 - jeffdill2
2
还要注意,如果h1h2{},它会引发错误。h.merge(**{},**{}) - tiagomenegaz

20

你只需要做

[*h,*h2,*h3].to_h
# => {:a=>1, :b=>2, :c=>3}

无论键是不是 Symbol,这都可以运行。


18

Ruby 2.6 允许 merge 接受多个参数:

h  = { a: 1 }
h2 = { b: 2 }
h3 = { c: 3 }
h4 = { 'c' => 4 }
h5 = {}

h.merge(h2, h3, h4, h5) # => {:a=>1, :b=>2, :c=>3, "c"=>4}

这也适用于Hash.merge!Hash.update。有关此内容的文档在此处

还可以接受空哈希和键作为符号或者字符串。

简单得多 :)


4
产生了 ArgumentError: wrong number of arguments (given 2, expected 1) 错误。 - Rajan Verma - Aarvy
3
你是否在使用 Ruby 2.6,@RajanVerma?这是 Ruby 2.6 中引入的功能;如果使用之前版本,则会出现你目前遇到的错误。 - SRack

8

使用reduce(与inject相同)来回答

hash_arr = [{foo: "bar"}, {foo2: "bar2"}, {foo2: "bar2b", foo3: "bar3"}]

hash_arr.reduce { |acc, h| (acc || {}).merge h }
# => {:foo2=>"bar2", :foo3=>"bar3", :foo=>"bar"}

解释

对于那些刚开始接触Ruby或函数式编程的人,我希望这个简短的解释可以帮助理解这里正在发生什么。

当在一个数组对象(hash_arr)上调用reduce方法时,它将迭代数组中的每个元素,并将块返回的值存储在累加器(acc)中。实际上,我的块的h参数将采用数组中每个哈希表的值,而acc参数将采用通过每次迭代返回的值。

我们使用(acc || {})来处理acc为空的初始条件。请注意,merge方法优先考虑原始哈希表中的键/值。这就是为什么"bar2b"的值没有出现在我的最终哈希表中的原因。

希望这可以帮到你!


1
仅仅因为我偶然遇到了这个。在 reduceinject 中,累加器(或备忘录)作为初始参数传递给块,而不是最后一个参数,这与此建议不同(不像 each_with_object)。 - engineersmnky
啊!谢谢@engineersmnky注意到这个问题。我已经更新了我的答案!太棒了! - Matt
1
这对我帮助很大。谢谢。 - User_coder

7
为了补充@Oleg Afanasyev的回答,您还可以尝试这个巧妙的技巧:
h  = { a: 1 }
h2 = { b: 2 }
h3 = { c: 3 }

z = { **h, **h2, **h3 }  # => {:a=>1, :b=>2, :c=>3}

干杯!


@CyrilDuchon-Doris 是的,这是这种方法的一个缺点。 - Tim Lowrimore

4
class Hash  
  def multi_merge(*args)
    args.unshift(self)
    args.inject { |accum, ele| accum.merge(ele) }
  end
end

那就这样吧。你可以像我展示的那样,轻松地将其作为Hash的猴子补丁插入其中。

3
newHash = [h, h2, h3].each_with_object({}) { |oh, nh| nh.merge!(oh)}
# => {:a=>1, :b=>2, :c=>3}

@AGS在这里做得对的一件事是使用#merge!而不是#merge。在这种情况下,#merge别名#update速度显著更快。 - Boris Stitnicky
对我来说很有效,因为我相信这里描述的大多数其他方法都不适用于Ruby v1.9。 - CTS_AE

3

这是我们应用程序中使用的两个猴子补丁的::Hash实例方法。支持Minitest规范。出于性能原因,它们在内部使用merge!而不是merge

class ::Hash

  # Merges multiple Hashes together. Similar to JS Object.assign.
  #   Returns merged hash without modifying the receiver.
  #
  # @param *other_hashes [Hash]
  #
  # @return [Hash]
  def merge_multiple(*other_hashes)
    other_hashes.each_with_object(self.dup) do |other_hash, new_hash|
      new_hash.merge!(other_hash)
    end
  end

  # Merges multiple Hashes together. Similar to JS Object.assign.
  #   Modifies the receiving hash.
  #   Returns self.
  #
  # @param *other_hashes [Hash]
  #
  # @return [Hash]
  def merge_multiple!(*other_hashes)
    other_hashes.each(&method(:merge!))

    self
  end

end

测试:

describe "#merge_multiple and #merge_multiple!" do
  let(:hash1) {{
    :a => "a",
    :b => "b"
  }}
  let(:hash2) {{
    :b => "y",
    :c => "c"
  }}
  let(:hash3) {{
    :d => "d"
  }}
  let(:merged) {{
    :a => "a",
    :b => "y",
    :c => "c",
    :d => "d"
  }}

  describe "#merge_multiple" do
    subject { hash1.merge_multiple(hash2, hash3) }

    it "should merge three hashes properly" do
      assert_equal(merged, subject)
    end

    it "shouldn't modify the receiver" do
      refute_changes(->{ hash1 }) do
        subject
      end
    end
  end

  describe "#merge_multiple!" do
    subject { hash1.merge_multiple!(hash2, hash3) }

    it "should merge three hashes properly" do
      assert_equal(merged, subject)
    end

    it "shouldn't modify the receiver" do
      assert_changes(->{ hash1 }, :to => merged) do
        subject
      end
    end
  end
end

2

只是为了好玩,你也可以这样做:

a = { a: 1 }, { b: 2 }, { c: 3 }
{}.tap { |h| a.each &h.method( :update ) }
#=> {:a=>1, :b=>2, :c=>3}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接