CSVファイルを読み込んで、同一キーのレコードの件数をwhile命令を使って合計する処理です。[2-2-4.同一キーでの件数カウント(until型)]がuntil命令で行うのに対し、こちらはwhile命令を使います。
ハッシュを使って、同一キーのレコードの中の数値項目を合計する処理です。
[2-2-3.同一キーでの合計処理(until型)]や[2-2-7.同一キーでの合計処理(while型)]ではuntilやwhileで繰り返しを行って、合計を計算するスクリプトについて解説しましたが、あらかじめキー順(昇順など)に並べ替えておく必要がありました。これに対し、ハッシュを使って行う合計処理では、あらかじめキー順に並べ替えて並べ替えておかなくても、合計処理ができるのが特徴です。
# total_hash.pl # 内容 : 同一キーの値を合計する # 前提 : 合計値を出すキーごとに昇順に並べ替えておく必要はない # Copyright (c) 2012 Mitsuo Minagawa, All rights reserved. # (minagawa@fb3.so-net.ne.jp) # 使用方法 : c:\>ruby total_hash.pl # # 出力用ハッシュ my %out1; open(IN1,"input.txt"); while($line1 = <IN1>) { #---区切り文字により、処理を変更する---------- #タブ区切りのとき # @in1 = split("\t",$line1); #カンマ区切りのとき my $tmp = $line1; $tmp =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @in1 = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); #----------------- $in1_key = $in1[0]; # ハッシュのキーがすでに存在する場合 if (exists $out1{$in1_key}) { @total = @{$out1{$in1_key}}; $total[0] += $in1[1]; $total[1] += $in1[3]; $out1{$in1_key} = [@total]; # 集計する項目が1つの場合 # $out1{$in1_key} += $in1[1]; } # ハッシュのキーが存在しない場合 else { $total[0] = $in1[1]; $total[1] = $in1[3]; $out1{$in1_key} = [@total]; # 集計する項目が1つの場合 # $out1{$in1_key} = $in1[1]; } } close(IN1); open(OUT1,">output.txt"); # ハッシュのキーをソート(並べ替え)しながら foreach my $key (sort keys %out1) { # 配列のリファレンスを配列に戻して出力する print OUT1 join("\t", $key, @{$out1{$key}})."\n"; } close(OUT1);
それでは、スクリプトの解説を個別に行っていきましょう。
入力データの配列へのセット
ここでは入力ファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目をハッシュのキーとし、CSVファイルの中で合計する項目をハッシュの値としてセットしています。なお、キーとなる項目は1番目の項目を想定していますが、必要であれば、異なる項目に変更します。
open(IN1,"input.txt"); while($line1 = <IN1>) { #---区切り文字により、処理を変更する---------- #タブ区切りのとき # @in1 = split("\t",$line1); #カンマ区切りのとき my $tmp = $line1; $tmp =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @in1 = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); #----------------- $in1_key = $in1[0]; # ハッシュのキーがすでに存在する場合 if (exists $out1{$in1_key}) { @total = @{$out1{$in1_key}}; $total[0] += $in1[1]; $total[1] += $in1[3]; $out1{$in1_key} = [@total]; # 集計する項目が1つの場合 # $out1{$in1_key} += $in1[1]; } # ハッシュのキーが存在しない場合 else { $total[0] = $in1[1]; $total[1] = $in1[3]; $out1{$in1_key} = [@total]; # 集計する項目が1つの場合 # $out1{$in1_key} = $in1[1]; } } close(IN1);
上記では、集計する項目をCSVファイルの2項目目($in1[1])と4項目目($in1[3])と想定していますが、集計する項目がそれ以上であれば、項目を追加していきます。また、集計する項目が1つの場合は、下記の部分を
# ハッシュのキーがすでに存在する場合 if (exists $out1{$in1_key}) { @total = @{$out1{$in1_key}}; $total[0] += $in1[1]; $total[1] += $in1[3]; $out1{$in1_key} = [@total]; } # ハッシュのキーが存在しない場合 else { $total[0] = $in1[1]; $total[1] = $in1[3]; $out1{$in1_key} = [@total]; } } close(IN1);
以下のように変更します。
# ハッシュのキーがすでに存在する場合 if (exists $out1{$in1_key}) { # 集計する項目が1つの場合 $out1{$in1_key} += $in1[1]; } # ハッシュのキーが存在しない場合 else { # 集計する項目が1つの場合 $out1{$in1_key} = $in1[1]; } } close(IN1);
ファイルの出力
最後に、ハッシュのキーをソート(並べ替え)して、出力します。
open(OUT1,">output.txt"); # ハッシュのキーをソート(並べ替え)しながら foreach my $key (sort keys %out1) { # 配列のリファレンスを配列に戻して出力する print OUT1 join("\t", $key, @{$out1{$key}})."\n"; } close(OUT1);
なお、集計する項目が1つの場合は、下記の箇所を
print OUT1 join("\t", $key, @{$out1{$key}})."\n";
以下のように変更します。
print OUT1 join("\t", $key, $out1{$key})."\n";
その他、注意すべきこと
その他、注意すべき点は入力データがタブ区切りなどの場合とキーが複数ある場合の処理です。
上記のスクリプトでは、入力ファイルがCSV2形式(CSV2形式については、[3-1-1.固定長データとCSVデータ]を参照してください)のCSVファイルであることを前提としていますが、どのようなCSVファイルにも対応できるようにするには、
@in1 = split(",",$line1,-1);
となっている箇所を以下のように変更します。
CSV形式の入力データを配列に変換する
my $tmp = $line1; $tmp =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @in1 = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g);
上記のスクリプトはCSV2形式以外の引用符(")を使用する場合を含め、あらゆるCSVファイルに対応できるようになっていますが、このCSVファイルに分解するロジックは、大崎 博基(OHZAKI Hiroki)さんの「Perlメモ」に記載されていたものを参考にしています。
また、入力ファイルがタブ区切りの場合は以下のようにします。
入力データがタブ区切りの場合、
@in1 = split("\t",$line1,-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