

以下の処理を`ruby`で行いたいのですが、やり方がわからず困っています。

複数の`[]`から`{}`を取り出し、ひとつの`[]`に格納する。ただし、同じ`id`をもつ`{}`は`num`を合計して一つにまとめる。

[code]

    data1 = [{
      'id' => 1,
      'num' => 1
    },{
      'id' => 2,
      'num' => 3
    },{
      'id' => 3,
      'num' => 5
    }]
    
    data2 = [{
      'id' => 1,
      'num' => 2
    },{
      'id' => 3,
      'num' => 4
    }]
    
    data3 = [{
      'id' => 1,
      'num' => 2
    },{
      'id' => 4,
      'num' => 4
    }]
    
[/code]

↓

[code]

    [{
      'id' => 1,
      'num' => 5
    },{
      'id' => 2,
      'num' => 3
    },{
      'id' => 3,
      'num' => 9
    },{
      'id' => 4,
      'num' => 4
    }]
    
[/code]


CC BY-SA 3.0
2015-05-10T02:08:10.380
9998
2015-08-08T13:24:26.750
2015-07-09T12:35:45.853
85
9149
question
ruby
配列の中のハッシュをidごとに値を合計した上で一つにまとめたい

[code]

    [data1, data2, data3].flatten.each_with_object([]) {|v, a|
      if ev = a.detect { |ev| v['id'] == ev['id'] }
        ev.merge!(v.merge('num' => v['num'] + ev['num']))
      else
        a << v.dup
      end
    }
    
[/code]

質問内容のコード例ではハッシュのキーは`id`と`num`だけでしたが、  
ハッシュのキーに`id`、`num`以外が含まれているか書かれていなかったのと、  
質問内容の以下の文で

> ただし、同じidをもつ{}はnumを合計して一つにまとめる。

`num`を合計して一つに「まとめる」と書いてあったので、

[code]

    data1 = [{
      'id' => 1,
      'num' => 1,
      'foo' => 'bar'
    },{
      'id' => 2,
      'num' => 3
    },{
      'id' => 3,
      'num' => 5
    }]
    
    data2 = [{
      'id' => 1,
      'num' => 2,
      'bar' => 'buzz'
    },{
      'id' => 3,
      'num' => 4
    }]
    
    data3 = [{
      'id' => 1,
      'num' => 2,
      'hoge' => 'piyo'
    },{
      'id' => 4,
      'num' => 4
    }]
    
    # => [{"id"=>1, "num"=>5, "foo"=>"bar", "bar"=>"buzz", "hoge"=>"piyo"}, {"id"=>2, "num"=>3}, {"id"=>3, "num"=>9}, {"id"=>4, "num"=>4}]
    
[/code]

上記のようなデータが入力されたときのことを考えてコードを書きました。


CC BY-SA 3.0
2015-05-10T03:11:49.360
9999
2015-05-10T05:18:36.150
2015-05-10T05:18:36.150
2599
2599
9998
answer

[code]

    data = [data1, data2, data3].flatten.group_by { |elem| elem['id'] }
    
    result_data = []
    data.each_pair do |key, arr|
      sum = arr.inject(0) { |memo, item| memo + item['num'] }
      result_data << { 'id' => key, 'num' => sum }
    end
    
    p result_data
    
[/code]

まずは、複数の配列を一つにまとめて、idごとに分類します。  
次に、分類されたそれぞれのグループの要素のnumの総和を求めます。  
最後に、結果を`result_data`に格納します。


CC BY-SA 3.0
2015-05-10T03:43:36.043
10000
2015-05-10T07:20:42.597
2015-05-10T07:20:42.597
7414
7414
9998
answer


こんな感じで、どうでしょう？一度、Hashにしてから、配列に戻しています。

[code]

    [data1, data2, data3].flatten.inject Hash.new do |a,e|
      h = { e['id'] => e['num'] }
      a.merge! h do |k, v1, v2|
        v1 + v2
      end
    end.inject [] do |a,e|
      a << { 'id' => e[0], 'num' => e[1] }
    end
    
[/code]


CC BY-SA 3.0
2015-05-10T03:45:18.267
10001
2015-05-10T03:45:18.267
9508
9998
answer


ハッシュのキーがidとnumだけであれば

[code]

    [data1, data2, data3].flatten.each_with_object({}) { |v, r|
      r[v['id']] ||= 0
      r[v['id']] += v['num']
    }.map { |id, num|
      {'id' => id, 'num' => num}
    }
    
[/code]


CC BY-SA 3.0
2015-05-10T05:10:44.260
10003
2015-05-10T05:55:25.137
2015-05-10T05:55:25.137
2599
2599
9998
answer


一時変数を使わずに書いてみました。

[code]

    (data1 + data2 + data3)
      .group_by {|h| h['id'] }
      .reduce([]) {|memo, (k, v)|
        memo << {'id' => k, 'num' => v.map {|h| h['num'] }.reduce(:+) } }
    
[/code]


CC BY-SA 3.0
2015-05-10T10:59:23.473
10007
2015-05-10T10:59:23.473
9608
9998
answer


面白そうなので僕もやってみました。  
ちょっと関数型プログラミングっぽいロジックになってます。

[code]

    require 'minitest/autorun'
    
    def merge_hash(*data_list)
      data_list
        .flatten
        .map(&:values)
        .each_with_object(Hash.new(0)) { |(id, num), hash| hash[id] += num }
        .map { |id, num| { 'id' => id, 'num' => num } }
    end
    
    describe '#merge_hash' do
      let(:data1) do
        [{
             'id' => 1,
             'num' => 1
         },{
             'id' => 2,
             'num' => 3
         },{
             'id' => 3,
             'num' => 5
         }]
      end
      let(:data2) do
        [{
             'id' => 1,
             'num' => 2
         },{
             'id' => 3,
             'num' => 4
         }]
      end
      let(:data3) do
        [{
             'id' => 1,
             'num' => 2
         },{
             'id' => 4,
             'num' => 4
         }]
      end
      let (:result) do
        [{
             'id' => 1,
             'num' => 5
         },{
             'id' => 2,
             'num' => 3
         },{
             'id' => 3,
             'num' => 9
         },{
             'id' => 4,
             'num' => 4
         }]
      end
      it do
        merge_hash(data1, data2, data3).must_equal result
      end
    end
    
[/code]

こんなやり方もあります。

[code]

    def merge_hash(*data_list)
      data_list
        .flatten
        .map(&:values)
        .group_by(&:first)
        .map{|id, items| { 'id' => id, 'num' => items.map(&:last).inject(:+) } }
    end
    
[/code]


CC BY-SA 3.0
2015-07-09T12:32:45.460
12214
2015-07-09T13:00:22.280
2015-07-09T13:00:22.280
85
85
9998
answer