SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

PerlによるCSVファイルの高速集計

PerlによるCSVファイルの高速集計

CSVを自在に扱って、業務を効率化しよう

  • X ポスト
  • このエントリーをはてなブックマークに追加

複雑なデータ型

 次はもう少し複雑な例です。「customer_data.csv」を、都道府県と市区ごとのメンバーが分かるように、以下のフォーマットでまとめてみましょう。

県・市ごとにまとめる
神奈川県
    横浜市
        50893,本間雅洋,29
        30876,三井武,18
    川崎市
        63487,堀井裕太,45
        02746,能美孝志,37
北海道
    苗穂町
        34249,合田由美,23
 :
 :

 この集計を行うためには、Perlで多重のデータ構造を可能とするのに利用される「リファレンス」の知識が不可欠となります。

リファレンス、無名配列、無名ハッシュ

 PerlにはリファレンスというC言語のポインタと似た概念があり、これを利用することで配列とハッシュを自由に入れ子にできます。例えば、次のような表現が可能です。

入れ子のデータ
$data{'千葉県'}{'松戸市'} = ['小林憲司', '高田美鈴', '荒田正人'];

 これは、千葉県の松戸市には、小林さんと高田さんと荒田さんが住んでいるというデータを表現したものです。あたかも、多重ハッシュの値が配列であるかのような構造です。リファレンスに関して理解することで、この用なデータ構造を扱うことができるようになります。

 リファレンスはC言語で言うポインタで、データを指し示すスカラ値です。配列、ハッシュに\を付けて取得することができます。

リファレンス
@arr  = (1, 2, 3);
%hash = (key1 => val1, key2 => val2);

# 以下の二つが、リファレンスとなる
$ref_arr  = \@arr;
$ref_hash = \%hash;

 リファレンスを元の配列・ハッシュと同等に扱うためには、次のように記述します。ちょうど配列の名前の部分に、{$ref}というように中括弧でくくったリファレンスを入れたかのような記述になります。

デリファレンス
# デリファレンスして配列に代入する
@arr2  = @{$ref_arr};

# デリファレンスして値を取り出す
print ${$ref_arr}[1], "\n";
print ${$ref_hash}{key2}, "\n";

 ただ、${$ref}[0]のようにリファレンスの先にある配列の要素を取り出す書式は非常によく使われるため、短縮系が用意されています。それが、アロー演算子「->」です。

アロー演算子
print $ref_arr->[1], "\n";
print $ref_hash->{key2}, "\n";

 ここまで来ると、先ほどの松戸市の例のデータ構造を作ることができます。実際に作ってみましょう。

松戸市のハッシュを作る
# 人が含まれた配列
@people = ('小林憲司', '高田美鈴', '荒田正人');
# 市区名をキーに、人を集めた配列を紐づける
%chiba  = ('松戸市' => \@people);
# 県名をキーに、市区名と人の配列を含むデータを紐づける
%data   = ('千葉県' => \%chiba);

 なんとかデータ構造を定義できましたが、ちょっと面倒くさいです。Perlではもっと直感的な定義が可能です。先ほどのコードでは、クッションとして@people%chibaという変数があるため、全体として、どのようなツリー構造が形成されているのか理解するのは大変です。そこで、今度は「無名リファレンス」と呼ばれる物でこれらの変数を使わないように書き換えます。

 無名リファレンスとは、配列名やハッシュ名をつけずにリファレンスを定義するというもので、[1, 2, 3]と書けば、(1, 2, 3)という配列を指し示すリファレンス値となり、{key => val}と中括弧で書くと、(key => val)というハッシュを指し示すリファレンス値となります。これらを使い、%dataを書き直すと以下のようになります。

無名ハッシュで書き直す
%data = (
  '千葉県' => {
                 '松戸市' => ['小林憲司', '高田美鈴', '荒田正人']
        }
);

 $data{'千葉'}->{'松戸'}とたどると、3人を含んだ配列のリファレンスにたどり着きそうだ、ということが分かりやすくなりました。なお、perlでは中括弧や大括弧間のアロー演算子が省略できるので、これを省略すると冒頭の式となります。

 このように、perlでは無名配列や無名ハッシュを入れ子にすることで、かなり幅広く直感的なデータ表現をすることが可能です。%dataにもう少しデータを詰め込めば、その威力が分かるでしょう。

自由なデータ構造
%data = (
  '千葉県' => {
                 '松戸市' => ['小林憲司', '高田美鈴', '荒田正人'],

                 '柏市'   => ['●●●●', '△△△△'],

                 '船橋市' => ['●●●●', '△△△△'],
        },
  '神奈川県' => {
                 '川崎市' => ['●●●●', '△△△△'],

                 '横浜市' => ['●●●●', '△△△△'],
        },
);

 ただ、これらのデータを本当に使いこなすには、先ほどのデリファレンスの知識が必須ですので、多少練習をしておく必要があります。習うより慣れろ、だと思うので、いろいろなデータ構造を自分の手で作ってみると早く習得できるでしょう。

県・市ごとに、データを分割する

 では、冒頭の集計に戻ります。「customer_data.csv」を、都道府県・市区ごとに集計するというものでした。この集計を実現するには、県と市区をキーとする多次元ハッシュの値として、人の情報を含む配列を指定できると、なんとかなりそうです。表示に関しては後から考えるとして、まずはデータ構造を構築する部分を書いてみましょう。

データ構造を作る
my %data = ();

open(IN, 'customer_data.csv');
while(<IN>){
    chomp;
    my ($id, $name, $age, $ken, $shi) = split(/,/, $_);

    # 人データを作る
    my $people = [$id, $name, $age];
    # 結果配列に保存する
    push(@{$data{$ken}{$shi}}, $people);
}
close(IN);

 人の情報は、$peopleとして無名配列を使いました。これを、$ken$shiをキーとする要素を配列と見なし、pushで追加しています。

表示させる

 %dataに蓄積されたデータを表示する部分を作りましょう。%dataは2次元のハッシュなので、まずはkeysによるループが二つ続きます。さらにその値は配列ですので、内側に配列のループが来ることになり、計3つのループ構造になりそうです。取り出した$peopleは、カンマで繋いで出力します。

データを表示する
# 出力ファイルを開く
open(OUT, ">customer_output.txt");

foreach my $ken (keys %data){
    # 県名の表示
    print OUT $ken, "\n";

    # 県名に対する値(市区がキーの連想配列)を取り出す
    my $ken_val = $data{$ken};

    foreach my $shi (keys %{$ken_val}){
        # 市区を表示
        print OUT "\t", $shi, "\n";

        # 県名・市区に対する値(人を含んだ配列)を取り出す
        my $shi_val = $data{$ken}{$shi};

    foreach my $people (@$shi_val){
        # カンマで区切って表示する
            print OUT "\t\t", join(',', @$people), "\n";
        }
    }
}

# ファイルを閉じる
close(OUT);

 出力結果は長くなるので、外部のファイルに吐き出しました。print文にファイルハンドル(OUT)を付け忘れないように気をつけてください。構築されたデータを読み込むループを作るときは、データを構築する時と同じ変数名を使うと、構造の理解に役に立ちます。ここでは、$ken$shi$peopleに同じ命名を使っています。

 これだけ複雑なデータを扱っても、ステップ数は20以下を保っています。多次元のデータ構造をこれほど簡潔に記述できる言語は少なく、この点に置いてもPerlはデータ処理に向いていると言えます。初めのうちは取り扱いに苦労するかもしれませんが、慣れるとどんな集計でもこなせるようなります。ぜひともチャレンジしてみてください。

まとめ

 Perlを使ったデータ処理に関して、以下の事柄を説明しました。

  • perlを使えば、テキストエディタとコマンドラインだけでCSVを高速に処理できる。
  • 基本はopenwhileループ、closeの構造で書くことができる。
  • split関数で、CSVの1行を配列に分けることができる。
  • 連想配列を利用して、グループ集計が可能である。
  • 連想配列を利用して、ユニークチェックが可能である。
  • リファレンスを用いて多次元のデータ構造を扱うことができる。

 Perlを使ったCSV処理で何より大事にしたいのは、手軽さをきちんと確保しておくことです。いくら強力な言語でも、コーディングや実行に手間がかかるようでは使う気にはなれません。いつも使うエディタのショートカットや、DOSプロンプトのショートカットなどを起動しやすい位置に集めておき、素早くスクリプトを実行することができる環境と手順を作っておきましょう。

次の記事

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
PerlによるCSVファイルの高速集計連載記事一覧
この記事の著者

hiratara(ヒラタラ)

1977年に苫小牧市で生まれる。北海道大学理学部数学科卒。小学生の頃、両親に買い与えられたMZ-2500でプログラミングを始めた。学生時代、CGIの自作に没頭し、それ以降WEB開発の魅力に憑かれる。社会人になっても数学好きは変わらず、専門書を買い集めるのが最近の趣味。id:hirataraにてblogを執筆...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/904 2007/03/19 20:07

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング