binarymason
binarymason

Reputation: 1433

How to "reduce" array of hashes with duplicate keys to nested hash?

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

Upvotes: 1

Views: 1113

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110675

def combine(arr)
  arr.group_by {|g|g[:foo]}.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).

Upvotes: 4

Ismail Moghul
Ismail Moghul

Reputation: 2984

This is what I came up:

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

h = {}
a.map(&:keys).uniq.flatten.each_with_index do |key, idx|
  h[key] = a.map(&:values).collect { |a| a[idx]}.uniq
end
h
#=> {:foo=>["dup"], :bar=>[1, 2, 3, 4, 5]}

Upvotes: 1

Jordan Running
Jordan Running

Reputation: 106027

If your data is really as simple as in your question, this will do what you want:

{ foo: "dup",
  bars: arr_with_dup_hsh_keys.map {|hsh| hsh[:bar] }
}

Upvotes: 1

Related Questions