連想配列を利用する
グループ集計
次に、「customer_data.csv」の各都道府県別人数を調べてみましょう。Perlでは言語の機能として「連想配列」(ハッシュと呼ばれることが多い)を持っており、簡単な記述でその機能をフルに使うことができます。このことが、Perlにおいてデータ集計処理を行う際の大きな武器となります。
ハッシュには、文字列のキーとそれに対応するスカラ値を格納することができます。都道府県をキーとし、そのカウント数を値とすることでグループ集計を行うことができます。ハッシュは先頭に%を付けた変数名で表すことができ、値にアクセスする場合は$と中括弧{}を用います。
my %count = (); open(IN, 'customer_data.csv'); while(<IN>){ chomp; my @d = split(/,/, $_); # 都道府県ごとにカウントアップ $count{$d[3]}++; } close(IN); #出力する foreach(keys %count){ print "$_,$count{$_}\n"; }
whileループの中で、$count{都道府県名}++のように都道府県ごとにカウントアップし、累計値を求めています。例えば、ファイルからの読み込みが終わった後の%countの内容は、次のようになっています。
$count{'北海道'} => 745
$count{'神奈川県'} => 2587
$count{'沖縄県'} => 487
:
:
この中身をすべて出力するのが、「#出力する」以下に書かれているコードです。foreachですべての要素を処理するのですが、単純にハッシュでループさせると、キー、値、キー、値……とキーと値が交互に返ってきてしまいます。しかし、表示にはキーごとのループの方が使いやすいので、keys関数によってキーだけを取り出しています。今回は、引き数省略時に使われる$_にキーが含まれているので、キー「$_」とその値「$count{$_}」を表示して終了です。このkeysとforeachを組み合わせた使い方はハッシュの処理の基礎になるので、このままの形で覚えてしまいましょう。
ユニークチェック
ところで、「customer_data.csv」にはたくさんの人のデータが含まれていますが、これらに同じ人は含まれていないのでしょうか? もし、ID:02547の佐藤大輔さんが2回含まれていたとすれば、佐藤大輔さんだけで北海道のカウントが2つもアップしてしまい、先ほどの都道府県ごとの集計は正しい物ではないということになってしまいます。
これについてPerlスクリプトを利用して検証してみましょう。顧客ID行に対して、このカラムに重複がないかをチェックしてみます。
ハッシュはユニークチェックにも威力を発揮します。次のようなスクリプトを書いてください。
my %id_flag = (); open(IN, 'customer_data.csv'); while(<IN>){ chomp; my @d = split(/,/, $_); # フラグが既に立っていれば重複なので終了 if($id_flag{$d[0]}){ die "行が重複しました。"; } # フラグを立てる $id_flag{$d[0]} = 1; } close(IN); print "OK\n";
if文で、顧客IDが既に%id_flagに登録されているかを判定しています。顧客IDが既に登録されていれば重複の発生なので、dieでエラー終了させます。新しい顧客IDの場合は、%id_flagに新たにそのIDを登録し、次の行のチェックへと進みます。このチェックが全行成功すれば、OKと表示して処理を終了します。
ところで、この表現は実はもう少し短く書くことができます。短い方が覚えやすく、打つのも楽であるため、筆者は次のような書式で暗記しています。
die "NG\n" if($id_flag{$d[0]}++ > 0);
Perlはif文を1行で書いた場合、ifを処理の後ろに書かねばならない点に注意してください。これは他の言語とは異なるため、慣れないと違和感があると思います。
さて、この書き方のミソは、後置のインクリメント「++」を使っているところです。一度目のIDの登場ではハッシュの中身は空であるため、値は0として評価されるのでこのif文は偽になります。そしてifの判定後にこのIDに対する値がカウントアップされるので、2回目以降登場してしまうとこのif文は真となり、dieの処理が実行されるわけです。
これ以外にも、if文の括弧を省略したり、unlessにしてexistsを使ったり等、いろいろな書き方がありますが、Perlは自分の書きやすいように書けるのが最大の利点です(欠点でもありますが……)。自分の覚えやすいような書き方で覚えるのが一番だと思います。
さて、ユニークチェックをした結果、残念ながらこのCSVには重複行が存在することが分かりました。「tdfk_count.pl」を修正して、正しいユニーク値を計算するにはどうすればいいでしょう? これは、先ほどのユニークチェックをうまく組み込めば簡単に実現できます。
my %id_flag = (); my %count = (); open(IN, 'customer_data.csv'); while(<IN>){ chomp; my @d = split(/,/, $_); # 重複している行は無視する next if($id_flag{$d[0]}++ > 0); # カウントする $count{$d[3]}++; } close(IN); #出力する foreach(keys %count){ print "$_,$count{$_}\n"; }
%id_flagを追加して、先ほどのユニーク判定のifを利用し、重複行が出現したらnextにより、その行を飛ばしているだけです。このようにパターンさえ覚えてしまえば、任意の組み合わせで、さまざまな種類の集計を実現することができます。
