Подтвердить что ты не робот

Как объединить несколько хэшей в Ruby?

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

Слияние хэша # работает для 2 хэшей: h.merge(h2)

Как слить 3 хэша?

h.merge(h2).merge(h3) работает, но есть ли лучший способ?

4b9b3361

Ответ 1

Вы можете сделать это следующим образом:

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)

Ответ 2

Так как 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}

Ответ 3

Вы можете просто сделать

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

Это работает независимо от того, являются ли клавиши Symbol s.

Ответ 4

Ответ, используя 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 или функционального программирования, я надеюсь, что это краткое объяснение поможет понять, что здесь происходит.

reduce метод при вызове на объекте Array (hash_arr) будет перебирать каждый элемент массива с возвращаемым значением блока хранится в накопителе ( в acc). По сути, параметр h моего блока будет принимать значение каждого хеша в массиве, а параметр acc будет принимать значение, которое возвращается блоком на каждой итерации.

Мы используем (acc || {}) для обработки начального условия, где acc равен нулю. Обратите внимание, что метод merge отдает приоритет ключам/значениям в исходном хеше. Вот почему значение "bar2b" не появляется в моем окончательном хеше.

Надеюсь, это поможет!

Ответ 5

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

Это должно сделать это. Вы могли бы легко обезвредить это в Хеш, как я показал.

Ответ 6

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. Документы для этого здесь.

Также принимает пустые хэши и ключи в виде символов или строк.

Гораздо проще :)

Ответ 7

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

Ответ 8

Вот два метода monkeypatched:: 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

Ответ 9

Чтобы ответить на ответ @Oleg Afanasyev, вы также можете сделать этот опрятный трюк:

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

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

Ура!

Ответ 10

Просто для удовольствия вы можете сделать это также следующим образом:

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