14

I have two arrays of hashes:

a = [
  {
    key: 1,
    value: "foo"
  },
  {
    key: 2,
    value: "baz"
  }
]

b = [
  {
    key: 1,
    value: "bar"
  },
  {
    key: 1000,
    value: "something"
  }
]

I want to merge them into one array of hashes, so essentially a + b except I want any duplicated key in b to overwrite those in a. In this case, both a and b contain a key 1 and I want the final result to have b's key value pair.

Here's the expected result:

expected = [
  {
    key: 1,
    value: "bar"
  },
  {
    key: 2,
    value: "baz"
  },
  {
    key: 1000,
    value: "something"
  }
]

I got it to work but I was wondering if there's a less wordy way of doing this:

hash_result = {}
a.each do |item|
  hash_result[item[:key]] = item[:value]
end

b.each do |item|
  hash_result[item[:key]] = item[:value]
end

result = []
hash_result.each do |k,v|
  result << {:key => k, :value => v}
end

puts result

puts expected == result # prints true
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Rhs
  • 3,188
  • 12
  • 46
  • 84
  • You want the functionality of a hash : you want unique keys and you want an easier way to merge your data. So just use hashes, as in @Ilya's answer. – Eric Duminil Apr 28 '17 at 20:48

5 Answers5

16

uniq would work if you concatenate the arrays in reverse order:

(b + a).uniq { |h| h[:key] }
#=> [
#     {:key=>1, :value=>"bar"},
#     {:key=>1000, :value=>"something"},
#     {:key=>2, :value=>"baz"}
#   ]

It doesn't however preserve the order.

Stefan
  • 109,145
  • 14
  • 143
  • 218
  • This is the best. – Aleksei Matiushkin Apr 28 '17 at 17:37
  • Order isn't important with hashes. – the Tin Man Apr 28 '17 at 17:43
  • 1
    @theTinMan `a` and `b` are arrays. Also, with ruby hashes order is preserved, and, hence, important. – Aleksei Matiushkin Apr 28 '17 at 19:26
  • 1
    No, order isn't important. Insertion order is preserved with a hash, but it's trivial to create a hash and extract values from it in any order desired. In many languages hashes do not maintain order, including previous versions of Ruby. Before hashes maintained that order everyone knew how to process them by ordering the keys. – the Tin Man Apr 29 '17 at 00:05
  • @theTinMan: Python will soon have ordered dicts by default : https://twitter.com/raymondh/status/850102884972675072 . It's very convenient, so why not? – Eric Duminil Apr 29 '17 at 21:42
2
[a, b].map { |arr| arr.group_by { |e| e[:key] } }
      .reduce(&:merge)
      .flat_map(&:last)

Here we use hash[:key] as a key to build the new hash, then we merge them overriding everything with the last value and return values.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
1

I would rebuild your data a bit, since there are redundant keys in hashes:

thin_b = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
thin_a = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}

Then you can use just Hash#merge:

thin_a.merge(thin_b)
#=> {1=>"bar", 2=>"baz", 1000=>"something"}

But, if you want, you can get exactly result as mentioned in question:

result.map { |k, v| { key: k, value: v } }
#=> [{:key=>1, :value=>"bar"}, 
#    {:key=>2, :value=>"baz"}, 
#    {:key=>1000, :value=>"something"}]
Ilya
  • 13,337
  • 5
  • 37
  • 53
  • That's not exactly what OP asked for but that's probably the best answer anyway. An alternative would be : `(a+b).each_with_object({}){ |s, h| h[s[:key]] = s[:value] }` – Eric Duminil Apr 28 '17 at 20:50
0

using Enumerable#group_by and Enumerable#map

(b+a).group_by { |e| e[:key] }.values.map {|arr| arr.first}
aqfaridi
  • 719
  • 7
  • 11
0

If you need to merge two arrays of hashes that should be merged also and there is more than two keys, then next snippet should help:

[a, b].flatten
      .compact
      .group_by { |v| v[:key] }
      .values
      .map { |e| e.reduce(&:merge) }
sawhikes
  • 1
  • 3