How & ldquo; Reduce & rdquo; Hashing set with duplicate keys for nested hash?

advertisements

Note: There were a few similar questions on SO about this, like here and here, but none seem quite like what I'm looking for.

Say I have an array of hashes like this:

arr_with_dup_hsh_keys = [
  { foo: "dup", bar: 1 },
  { foo: "dup", bar: 2 },
  { foo: "dup", bar: 3 },
  { foo: "dup", bar: 4 },
  { foo: "dup", bar: 5 }
]

How do I reduce that down to this?

{ foo: "dup", bars: [1, 2, 3, 4, 5] }

And what if there are different values for foo?

arr_with_dup_hsh_keys = [
  { foo: "dup",  bar: 1 },
  { foo: "dup",  bar: 2 },
  { foo: "soup", bar: 3 },
  { foo: "dup",  bar: 4 },
  { foo: "soup", bar: 5 }
]


def combine(arr)
  arr.group_by {|g|g[:foo]}.values.map {|a|{foo: a.first[:foo], bar: a.map {|g| g[:bar]}}}
end

combine arr_with_dup_hsh_keys
  #=> [{:foo=>"dup", :bar=>[1, 2, 3, 4, 5]}]

arr_with_dup_hsh_keys1 = [
  { foo: "dup",  bar: 1 },
  { foo: "dup",  bar: 2 },
  { foo: "soup", bar: 3 },
  { foo: "dup",  bar: 4 },
  { foo: "soup", bar: 5 }
]

combine arr_with_dup_hsh_keys1
  #=> [{:foo=>"dup", :bar=>[1, 2, 4]}, {:foo=>"soup", :bar=>[3, 5]}]

See Enumerable#group_by and note that

arr_with_dup_hsh_keys1.group_by { |g| g[:foo] }
 #=> {"dup"=> [{:foo=>"dup", :bar=>1}, {:foo=>"dup", :bar=>2},
 #             {:foo=>"dup", :bar=>4}],
 #    "soup"=>[{:foo=>"soup", :bar=>3}, {:foo=>"soup", :bar=>5}]}

You could alternatively write the following.

def combine(arr)
  arr.each_with_object({}) do |g,h|
    f = g.merge(bar: [g[:bar]])
    h.update(f[:foo]=>f) { |_,o,n| { foo: o[:foo], bar: o[:bar]+n[:bar] } }
  end.values
end

combine arr_with_dup_hsh_keys1
  #=> [{:foo=>"dup", :bar=>[1, 2, 4]}, {:foo=>"soup", :bar=>[3, 5]}]

This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for an explanation of the three block variables (the first being the common key, which I've represented with an underscore to signify that it's not used in the block calculation).