Javaでデータを集計する処理について書かれたブログを読んだよ。

リストを項目ごとに集計する - 日々常々

内容は次のようなデータがあって、

code name value
A01 hoge 100
A01 piyo 200
A02 hoge 300
A03 hoge 400
A03 piyo 500

次のような集計の結果を得たいというものだよ。

code value
A01 300
A02 300
A03 900

僕はRubyしか知らないからRubyでやってみるけど、RubyにはEnumerable#injectがあるから集計処理は簡単にできるよ。ここではデータの読み込みからやってみることにするよ。先のデータがテキストファイルにあると仮定するよ。

まずはデータを読み込んでlabelとdataの配列に格納しよう。

label, *data = ARGF.map do |line|
  line.split.map(&:strip).map { |d| d.match(/^[\d,]+$/) ? d.delete(',').to_i : d.intern }
end
label # => [:code, :name, :value]
data # => [[:A01, :hoge, 100], [:A01, :piyo, 200], [:A02, :hoge, 300], [:A03, :hoge, 400], [:A03, :piyo, 500]]

ここでは次のような処理をしているよ。

  1. 引数として渡されるデータファイルをARGFに読み込む。
  2. データの各ラインに対して、splitでデータを分ける。
  3. データが数値文字なら数値に変換し、それ以外ならシンボルに変換する。
  4. 先頭行の結果をlabelに、残りをdataに代入する。

次にこの配列データをオブジェクト化するよ。

Product = Struct.new(*label)
products = data.map { |d| Product.new(*d) }
products # => [#<struct Product code=:A01, name=:hoge, value=100>, #<struct Product code=:A01, name=:piyo, value=200>, #<struct Product code=:A02, name=:hoge, value=300>, #<struct Product code=:A03, name=:hoge, value=400>, #<struct Product code=:A03, name=:piyo, value=500>]

属性だけのオブジェクトを作るのはStructが便利だね。

そして集計するよ。

puts products.inject(Hash.new(0)) { |h, pr| h[pr.code] += pr.value; h }
# >> {:A01=>300, :A02=>300, :A03=>900}

これはもう見慣れたコードだよね。ハッシュは0で初期化されるようにして、それからinjectはハッシュhを返すように。

コードをまとめると次のようになるよ。

このコードはわりと汎用性があるんだよ。たとえば次のようなデータに適用する場合、

datecategorysubexpense
2011/1食費肉類4,289
2011/1食費魚介類4,036
2011/1食費野菜・果物3,332
2011/1光熱費電気代6,689
2011/1光熱費ガス代4,792
2011/1光熱費水道代4,290
2011/1娯楽費ゲーム・CD等2,913
2011/1娯楽費入場料191
2011/1娯楽費ピアノ1200
2011/1娯楽費その他1,376
2011/1交通費電車代1,286
2011/1交通費バス代350
2011/1交通費タクシー1270
2011/1医療費治療代913
2011/1医療費薬代945
2011/2食費肉類2,186
2011/2食費魚介類1,111
2011/2光熱費電気代3,645
2011/2光熱費ガス代6,912
2011/2光熱費水道代4,123
2011/2娯楽費ゲーム・CD等5,900
2011/2娯楽費ピアノ1200
2011/2娯楽費その他3,034
2011/2交通費電車代2,286
2011/2交通費バス代450
2011/2交通費タクシー0
2011/2医療費治療代1,988
2011/2医療費薬代650

集計コードのラベルだけを変えればいいんだ。

puts products.inject(Hash.new(0)) { |h, pr| h[pr.category] += pr.expense; h }
# >> {:食費=>14954, :光熱費=>30451, :娯楽費=>15814, :交通費=>5642, :医療費=>4496}

月毎の支出を見たい場合はこうするよ。

puts products.inject(Hash.new(0)) { |h, pr| h[pr.date] += pr.expense; h }
# >> {:"2011/1"=>37872, :"2011/2"=>33485}

Rubyは便利だね!



blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS