■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-6.ハッシュを使ったマージ処理

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

[2-3-5.ハッシュを使った1対1マッチング(照合)]を変形した処理で、「マスタファイル」と「トランザクションファイル」の両方から統合した1つのファイルを作成する処理として[2-3-3.マージ処理]で取り上げましたが、同様の処理についてハッシュを使って行う方法を以下に挙げます。[2-3-3.マージ処理]と異なる点は、入力データがキー順に並べ替えてある必要がない点です。

注意すべき点は、出力ファイルにあわせてファイルハンドルを設定する点です。この例では、出力ファイルは1つのため、対応するファイルハンドルも1つになっています。仮に「マッチング(照合)したファイル」と「トランザクションファイル」を1つのファイルに出力し、「マスタオンリーファイル」と区別する場合は、ファイルハンドルを2つ設定することになります。マッチング(照合)用のスクリプトをそのまま持ってきて、ファイル名だけを変更すると誤った出力結果になるので注意する必要があります。

統合された1つのファイルに対して、「マスタファイル」や「トランザクションファイル」からどのようにデータをセットするかは、それぞれのケースによって異なるので、各自工夫してください。

「1対1マッチング(照合)」の変形だからといって、複数の異なる出力ファイルのファイルハンドルに対して、同一のデータを指定しないでください。処理の結果が保証されなくなりますので、十分注意してください。

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

# マスタデータをハッシュに入れる
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) {   
#       $out1{$key} =   $data1{$key};   
#}  

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

#トランザクションのデータを優先させる場合   
foreach my $key (sort keys %data2) {    
        $out1{$key} =   $data2{$key};   
}   

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

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

   
【スクリプトとデータのサンプル】

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

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

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

【スクリプトの解説】

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

マスタファイルの入力

ここではマスタファイルを入力し、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) {   
           $out1{$key} =   $data1{$key};   
    }  
    
    foreach my $key (sort keys %data2) {   
    # ハッシュのキーが存在しない場合(トランザクションにしかないとき)    
       unless  (exists $data1{$key})   {   
           $out1{$key} =   $data2{$key};   
       }   
    }  
   

逆にトランザクションファイルを優先させる場合は、以下のようにします。まず、トランザクションのデータを出力用のハッシュにセットした後に、トランザクションファイルにない場合だけ、マスタファイルのデータをセットします。

    #トランザクションのデータを優先させる場合   
    foreach my $key (sort keys %data2) {    
            $out1{$key} =   $data2{$key};   
    }   
    
    foreach my $key (sort keys %data1) {    
    #ハッシュのキーが存在しない場合(マスタにしかないとき)   
        unless  (exists $data2{$key})   {   
            $out1{$key} =   $data1{$key};   
        }   
    }   
   

出力処理

最後にマージしたハッシュからデータを出力します。その際、「@{$out1{$key}}」とすることで、配列のリファレンスを配列に変換します。

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

その他注意すべきこと

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

上記のスクリプトでは、入力ファイルが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
   
【出力データ(merge.txt)】
01  11111   
02  22222
03  33333
04  400
05  44444
06  600
07  77777
08  88888
09  99999
10  1000
   



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