■Windows版Perlの細道・けもの道

■ナビゲータ

[南北館(最初のメニュー)]

  1. [Windows版Perlの細道・けもの道]
    1. [1.準備編]
    2. [2.基本編]
      1. [2-1.基本処理]
      2. [2-2.キーブレイク処理]
      3. [2-3.マッチング(照合)処理]
        1. [2-3-1.マッチング(照合)処理の概要]
        2. [2-3-2.1対1マッチング(照合)]
        3. [2-3-3.マージ処理]
        4. [2-3-4.1対nマッチング(照合)]
        5. [2-3-5.ハッシュを使った1対1マッチング(照合)]
        6. [2-3-6.ハッシュを使ったマージ処理]
        7. [2-3-7.ハッシュを使った1対nマッチング(照合)]
      4. [2-4.ソート(並べ替え)処理]
      5. [2-5.パターンマッチ処理]
    3. [3.応用編]
    4. [スクリプトと入力データのサンプル]
rubyではどう処理する?
同じことをrubyではこうしています。

2.基本編

2-3.マッチング(照合)処理

2-3-5.ハッシュを使った1対1マッチング(照合)

2つのCSVファイルを読み込んで、ハッシュを使って、同一のキー項目を元に照合を行うスクリプトです。

「マスタファイル」も「トランザクションファイル」もそれぞれ、同一のキーを持つレコードが1つしか存在しない場合のマッチング(照合)処理は[2-3-2.1対1マッチング(照合)]で取り上げましたが、同様の処理についてハッシュを使って行う方法を以下に挙げます。[2-3-2.1対1マッチング(照合)]と異なる点は、入力データがキー順に並べ替えてある必要がない点です。

マッチング(照合)した場合は、「マスタファイル」と「トランザクションファイル」の両方が元データになりますが、どちらのファイルのデータをどのように編集するかは、それぞれのケースによって異なるので、各自工夫してください。

「マスタオンリーの処理」と「トランザクションオンリーの処理」では、それぞれ「マスタファイル」や「トランザクションファイル」からすべてのデータをセットして、出力していますが、必要に応じて、条件をつけたり、集計したり、必要な項目だけをセットするなど、内容を変更して利用することになります。

【スクリプト】
# matching1_hash.pl 
# 内容 : マッチングプログラム(2ファイルの1対1マッチングプログラム) 
# 前提 : マスタファイルとトランザクションファイルの両方とも 
#        マッチングするキーごとに昇順に並べ替えておく必要はない 
# Copyright (c) 2012 Mitsuo Minagawa, All rights reserved.  
# (minagawa@fb3.so-net.ne.jp)   
# 使用方法 : c:\>perl matching1_hash.pl 
#   

# マスタデータを入れるハッシュ    
my %data1;  
# トランザクションデータを入れるハッシュ    
my %data2;  
# マッチング(照合)したデータを入れるハッシュ  
my %out1;   
# マスタのみのデータを入れるハッシュ    
my %out2;   
# トランザクションのみのデータを入れるハッシュ  
my %out3;   

# マスタデータをハッシュに入れる
open(IN1,"master.txt"); 
while($line1    =   <IN1>) {    
    chomp($line1);  
#---区切り文字により、処理を変更する----------  
#タブ区切りのとき   
#   @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];    
# 配列のリファレンスを生成し、ハッシュに配列を代入する  
    $data1{$in1_key} = [@in1];  
}   
close(IN1); 

# トランザクションデータをハッシュに入れる
open(IN2,"transaction.txt");    
while($line2    =   <IN2>) {    
    chomp($line2);  
#---区切り文字により、処理を変更する----------  
#タブ区切りのとき   
#   @in2    =   split("\t",$line2); 

#カンマ区切りのとき 
    my $tmp =   $line2; 
    $tmp    =~  s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; 
    @in2    =   map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_}   
                            ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g);  
#-----------------  
    $in2_key    =   $in2[0];    
# 配列のリファレンスを生成し、ハッシュに配列を代入する  
    $data2{$in2_key} = [@in2];  
}   
close(IN2); 

foreach my $key (sort keys %data1) {    
# ハッシュのキーがすでに存在する場合(マッチしたとき)    
    if  (exists $data2{$key})   {   
        $out1{$key} =   $data1{$key};   
    }   
# ハッシュのキーが存在しない場合(マスタにしかないとき)  
    else    {   
        $out2{$key} =   $data1{$key};   
    }   
}   

foreach my $key (sort keys %data2) {    
# ハッシュのキーが存在しない場合(トランザクションにしかないとき)    
    unless  (exists $data1{$key})   {   
        $out3{$key} =   $data2{$key};   
    }   
}   

open(OUT1,">matching.txt"); 
# ハッシュのキーをソート(並べ替え)しながら
foreach my $key (sort keys %out1) { 
# 配列のリファレンスを配列に戻して出力する  
    print OUT1 join("\t", @{$out1{$key}})."\n"; 
}   
close(OUT1);    

open(OUT2,">masteronly.txt");   
# ハッシュのキーをソート(並べ替え)しながら
foreach my $key (sort keys %out2) { 
# 配列のリファレンスを配列に戻して出力する  
    print OUT2 join("\t", @{$out2{$key}})."\n"; 
}   
close(OUT2);    

open(OUT3,">tranonly.txt"); 
# ハッシュのキーをソート(並べ替え)しながら
foreach my $key (sort keys %out3) { 
# 配列のリファレンスを配列に戻して出力する  
    print OUT3 join("\t", @{$out3{$key}})."\n"; 
}   
close(OUT3);    
   
【スクリプトとデータのサンプル】

スクリプトはこちらにあります。

マスタファイルのサンプルはこちらにあります。

トランザクションファイルのサンプルはこちらにあります。

【スクリプトの解説】

それでは、スクリプトの解説を個別に行っていきましょう。

マスタファイルの入力

ここではマスタファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目をハッシュのキーとし、CSVファイルを配列にしたものをハッシュの値としてセットしています。なお、キーとなる項目は1番目の項目を想定していますが、必要であれば、異なる項目に変更します。

    # マスタデータをハッシュに入れる
    open(IN1,"master.txt"); 
    while($line1    =   <IN1>) {    
        chomp($line1);  
    #---区切り文字により、処理を変更する----------  
    #タブ区切りのとき   
    #   @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];    
    # 配列のリファレンスを生成し、ハッシュに配列を代入する  
        $data1{$in1_key} = [@in1];  
    }   
    close(IN1); 
   

上記では、入力したCSVファイルを各項目をすべてハッシュの値としてセットしていますが、入力したCSVファイルの各項目の一部をハッシュの値としてセットする場合は、

    $data1{$in1_key} = [@in1];  
   

の部分を以下のように変更します。

    $data1{$in1_key} = [$in1[1],$in1[2]];  
   

トランザクションファイルの入力

次にトランザクションファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目をハッシュのキーとし、CSVファイルを配列にしたものをハッシュの値としてセットしています。なお、キーとなる項目は1番目の項目を想定していますが、必要であれば、異なる項目に変更します。

    # トランザクションデータをハッシュに入れる
    open(IN2,"transaction.txt");    
    while($line2    =   <IN2>) {    
        chomp($line2);  
    #---区切り文字により、処理を変更する----------  
    #タブ区切りのとき   
    #   @in2    =   split("\t",$line2); 
    
    #カンマ区切りのとき 
        my $tmp =   $line2; 
        $tmp    =~  s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; 
        @in2    =   map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_}   
                                ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g);  
    #-----------------  
        $in2_key    =   $in2[0];    
    # 配列のリファレンスを生成し、ハッシュに配列を代入する  
        $data2{$in2_key} = [@in2];  
    }   
    close(IN2); 
   

上記では、入力したCSVファイルを各項目をすべてハッシュの値としてセットしていますが、入力したCSVファイルの各項目の一部をハッシュの値としてセットする場合は、

    $data2{$in2_key} = [@in2];  
   

の部分を以下のように変更します。

    $data2{$in2_key} = [$in2[1],$in2[2]];  
   

マッチング(2つのファイルの照合)処理

次に入力した2つのデータを照合し、マスタファイルとトランザクションファイルの両方に存在するか、それともどちらか一方にしか存在しないかによって、データを振り分けて別のハッシュにセットします。

ここではマスタファイルをハッシュのキー順に並べ替えながら、マスタファイルのキーがトランザクションファイルに存在するかどうかをチェックし、存在する場合(マッチしたとき)は、マッチング(照合)データを入れるハッシュにセットし、マスタファイルのキーがトランザクションファイルに存在しない場合(マスタにしかないとき)は、マスタのみのデータを入れるハッシュにセットします。

    foreach my $key (sort keys %data1) {    
    # ハッシュのキーがすでに存在する場合(マッチしたとき)    
        if  (exists $data2{$key})   {   
            $out1{$key} =   $data1{$key};   
        }   
    # ハッシュのキーが存在しない場合(マスタにしかないとき)  
        else    {   
            $out2{$key} =   $data1{$key};   
        }   
    }   
   

以下では、トランザクションファイルをハッシュのキー順に並べ替えながら、トランザクションファイルのキーがマスタファイルに存在するかどうかをチェックし、存在しない場合(トランザクションにしかないとき)は、トランザクションのみのデータを入れるハッシュにセットします。

    foreach my $key (sort keys %data2) {    
    # ハッシュのキーが存在しない場合(トランザクションにしかないとき)    
        unless  (exists $data1{$key})   {   
            $out3{$key} =   $data2{$key};   
        }   
    }   
   

なお、上記ではマスタファイルとトランザクションファイルの両方に同一のキー項目が存在した場合には、マスタファイルにあるデータを優先させていますが、これをトランザクションファイルにあるデータを優先させるようにするには、以下の部分を

    foreach my $key (sort keys %data1) {    
    # ハッシュのキーがすでに存在する場合(マッチしたとき)    
        if  (exists $data2{$key})   {   
            $out1{$key} =   $data1{$key};   
        }   
    # ハッシュのキーが存在しない場合(マスタにしかないとき)  
        else    {   
            $out2{$key} =   $data1{$key};   
        }   
    }   

    foreach my $key (sort keys %data2) {    
    # ハッシュのキーが存在しない場合(トランザクションにしかないとき)    
        unless  (exists $data1{$key})   {   
            $out3{$key} =   $data2{$key};   
        }   
    }   
   

下記のように変更します。

    foreach my $key (sort keys %data2) {    
    # ハッシュのキーがすでに存在する場合(マッチしたとき)    
        if  (exists $data1{$key})   {   
            $out1{$key} =   $data2{$key};   
        }   
    # ハッシュのキーが存在しない場合(トランザクションにしかないとき)  
        else    {   
            $out3{$key} =   $data2{$key};   
        }   
    }   

    foreach my $key (sort keys %data1) {    
    # ハッシュのキーが存在しない場合(マスタにしかないとき)    
        unless  (exists $data2{$key})   {   
            $out2{$key} =   $data1{$key};   
        }   
    }   
   

出力処理

最後に振り分けたそれぞれのファイルをキー順に出力していきます。下記ではタブ区切りで出力していますが、必要に応じて他の区切り文字を使って出力することもできます。

    open(OUT1,">matching.txt"); 
# ハッシュのキーをソート(並べ替え)しながら
    foreach my $key (sort keys %out1) { 
    # 配列のリファレンスを配列に戻して出力する  
        print OUT1 join("\t", @{$out1{$key}})."\n"; 
    }   
    close(OUT1);    

    open(OUT2,">masteronly.txt");   
# ハッシュのキーをソート(並べ替え)しながら
    foreach my $key (sort keys %out2) { 
    # 配列のリファレンスを配列に戻して出力する  
        print OUT2 join("\t", @{$out2{$key}})."\n"; 
    }   
    close(OUT2);    

    open(OUT3,">tranonly.txt"); 
# ハッシュのキーをソート(並べ替え)しながら
    foreach my $key (sort keys %out3) { 
    # 配列のリファレンスを配列に戻して出力する  
        print OUT3 join("\t", @{$out3{$key}})."\n"; 
    }   
    close(OUT3);    
   

その他注意すべきこと

その他、注意すべき点は入力データがタブ区切りなどの場合とキーが複数ある場合の処理です。

上記のスクリプトでは、入力ファイルがCSVファイルであれば、どのようなものでも対応できるようになっています。具体的には以下の箇所になります。

CSV形式の入力データを配列に変換する

    my $tmp =   $line1; 
    $tmp    =~  s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; 
    @in1    =   map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_}   
                ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g);  
   

上記のスクリプトはCSV2形式以外の引用符(")を使用する場合を含め、あらゆるCSVファイルに対応できるようになっています(CSV2形式などについては、[3-1-1.固定長データとCSVデータ]を参照してください)が、このCSVファイルに分解するロジックは、大崎 博基(OHZAKI Hiroki)さんの「Perlメモ」に記載されていたものを参考にしています。また、CSV2形式のCSVファイルであれば、上記のスクリプトは以下のように簡略化できます。

1.入力データがCSV2形式の場合、

          @in1 = split(",",$line1,-1);
   

とします。

2.入力データがタブ区切りの場合、

          @in1 = split("\t",$line1,-1);
   

とします。

3.キーが複数の項目から成り立っている場合は、

「$in1_key = $in1[0]」の部分を

          $in1_key = $in1[0].$in1[1].$in1[2];
   

のように変更します(文字列の連結は「.」(ピリオド)を使います)。

【入力データ:マスタファイル(master.txt)】
01,11111   
02,22222
03,33333
05,44444
07,77777
08,88888
09,99999
   
【入力データ:トランザクションファイル(transaction.txt)】
01,100   
02,200
04,400
06,600
07,700
08,800
09,900
10,1000
   
【出力データ:マッチング(照合)ファイル(matching.txt)】
01  11111   
02  22222
07  77777
08  88888
09  99999
   
【出力データ:マスタオンリーファイル(masteronly.txt)】
03  33333   
05  44444
   
【出力データ:トランザクションオンリーファイル(tranonly.txt)】
04  400   
06  600
10  1000
   



Copyright (c) 2012-2013 Mitsuo Minagawa, All rights reserved.