CSVファイルを読み込んで、同一キーのレコードの中の数値項目をハッシュを使って合計する処理です。
[2-2-3.同一キーでの合計処理(until型)]や[2-2-7.同一キーでの合計処理(while型)]ではuntilやwhileで繰り返しを行って、合計を計算するスクリプトについて解説しましたが、あらかじめキー順(昇順など)に並べ替えておく必要がありました。これに対し、ハッシュを使って行う合計処理では、あらかじめキー順に並べ替えて並べ替えておかなくても、合計処理ができるのが特徴です。
# total_hash.rb
# 内容 : 同一キーの値を合計する
# 前提 : 合計値を出すキーごとに昇順に並べ替えておく必要はない
# Copyright (c) 2012-2015 Mitsuo Minagawa, All rights reserved.
# (minagawa@fb3.so-net.ne.jp)
# 使用方法 : c:\>ruby total_hash.rb
#
# 入力ファイル
in1_file = open("input.txt","r")
# 出力ファイル
out1_file = open("output.txt","w")
# 出力ファイル用ハッシュ
out1 = Hash.new
# 合計用の配列
w_total = Array.new
while (line1 = in1_file.gets)
line1.chomp!
# タブ区切りのとき
# in1 = line1.split("\t",-1)
# カンマ区切りのとき
in1 = (line1 + ',')
.scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)
.collect{|x,y| y || x.gsub(/(.)/, '\1')}
# キー項目が単独のとき
in1_key = in1[0]
# キー項目が複数のとき
# 1番目と2番目と3番目の項目がキーとなる場合
# in1_key = in1[0] + in1[1] + in1[2]
# ハッシュのキーがすでに存在する場合
if (out1.include?(in1_key))
w_total = out1[in1_key]
w_total[0] += in1[1].to_i
w_total[1] += in1[3].to_i
out1[in1_key] = w_total
# 集計する項目が1つの場合
# out1[in1_key] += in1[1].to_i
# ハッシュのキーが存在しない場合
else
w_total = Array.new
w_total[0] = in1[1].to_i
w_total[1] = in1[3].to_i
out1[in1_key] = w_total
# 集計する項目が1つの場合
# out1[in1_key] = in1[1].to_i
end
end
# ハッシュのキーをソートして出力する
out1.sort_by{|key| key }.each{|key,value|
w_temp = [key,value].join("\t")
out1_file.print "#{w_temp}\n"
}
# ファイルのクローズ
in1_file.close
out1_file.close
それでは、スクリプトの解説を個別に行っていきましょう。
入力データの配列へのセット
ここでは入力ファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目をハッシュのキーとし、CSVファイルの中で合計する項目をハッシュの値としてセットしています。なお、キーとなる項目は1番目の項目を想定していますが、必要であれば、異なる項目に変更します。
in1_file = open("input.txt","r")
while (line1 = in1_file.gets)
line1.chomp!
# タブ区切りのとき
# in1 = line1.split("\t")
# カンマ区切りのとき
in1 = (line1 + ',')
.scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)
.collect{|x,y| y || x.gsub(/(.)/, '\1')}
in1_key = in1[0]
# ハッシュのキーがすでに存在する場合
if (out1.include?(in1_key))
total = out1[in1_key]
total[0] += in1[1].to_i
total[1] += in1[3].to_i
out1[in1_key] = total
# 集計する項目が1つの場合
# out1[in1_key] += in1[1].to_i
# ハッシュのキーが存在しない場合
else
total = Array.new
total[0] = in1[1].to_i
total[1] = in1[3].to_i
out1[in1_key] = total
# 集計する項目が1つの場合
# out1[in1_key] = in1[1].to_i
end
end
in1_file.close
上記では、集計する項目をCSVファイルの2項目目(in1[1])と4項目目(in1[3])と想定していますが、集計する項目がそれ以上であれば、項目を追加していきます。また、集計する項目を整数であると想定しているため、「in1[1].to_i」のようにしていますが、実数を集計する場合には、「in1[1].to_f」とします。さらに、集計する項目が1つの場合は、下記の部分を
# ハッシュのキーがすでに存在する場合
if (out1.include?(in1_key))
total = out1[in1_key]
total[0] += in1[1].to_i
total[1] += in1[3].to_i
out1[in1_key] = total
# ハッシュのキーが存在しない場合
else
total = Array.new
total[0] = in1[1].to_i
total[1] = in1[3].to_i
out1[in1_key] = total
end
end
以下のように変更します。
# ハッシュのキーがすでに存在する場合
if (out1.include?(in1_key))
# 集計する項目が1つの場合
out1[in1_key] += in1[1].to_i
# ハッシュのキーが存在しない場合
else
# 集計する項目が1つの場合
out1[in1_key] = in1[1].to_i
end
end
ファイルの出力
最後に、ハッシュのキーをソート(並べ替え)して、出力します。
# ハッシュのキーをソートして出力する
out1.sort_by{|key| key }.each{|key,value|
w_temp = [key,value].join("\t")
out1_file.print "#{w_temp}\n"
}
その他注意すべきこと
その他、注意すべき点は入力データがタブ区切りなどの場合とキーが複数ある場合の処理です。
上記のスクリプトでは、入力ファイルがCSV形式であれば、すべて対応できるようになっていますが、引用符(")をつけないCSV形式(いわゆるCSV2形式と呼ばれるものです。CSV2形式については、[3-1-1.固定長データとCSVデータ]を参照してください)であれば、
# カンマ区切りのとき
in1 = (line1 + ',')
.scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)
.collect{|x,y| y || x.gsub(/(.)/, '\1')}
上記の箇所を
in1 = line1.split(",",-1)
と変更します。
また、入力ファイルがタブ区切りの場合は以下のようにします。
入力データがタブ区切りの場合、
in1 = line1.split("\t",-1)
とします。
キーが複数の項目から成り立っている場合は、
「$in1_key = $in1[0]」の部分を
in1_key = in1[0]+in1[1]+in1[2]
のように変更します(文字列の連結は「+」(プラス)を使います)。
aaa,1 aaa,2 aaa,3 bbb,1 ccc,1 ccc,2 ddd,1 ddd,2 ddd,3 ddd,4 eee,1 fff,1 ggg,1 hhh,1 hhh,2 hhh,3
aaa,6 bbb,1 ccc,3 ddd,10 eee,1 fff,1 ggg,1 hhh,6