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

■ナビゲータ

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

  1. [Windows版Rubyの細道・けもの道]
    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. [スクリプトと入力データのサンプル]
Perlではどう処理する?
同じことをPerlではこうしています。

2.基本編

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

2-3-3.マージ処理

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

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

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

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

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

# ファイルのオープン    
in1_file    =   open("master.txt","r")  #マスターファイル   
in2_file    =   open("transaction.txt","r") #トランザクションファイル   
out1_file   =   open("merge.txt","w")   #マージファイル 

# マスターデータを入れるハッシュ    
data1   =   Hash.new    
# トランザクションデータを入れるハッシュ    
data2   =   Hash.new    
# マージしたデータを入れるハッシュ  
out1    =   Hash.new    

# マスターファイルの入力    
while   (line1  =   in1_file.gets)  
        line1.chomp!    
# タブ区切りのとき  
#       in1     =   line1.split("\t",-1)    
# カンマ区切りのとき    
        in1     =   (line1 + ',')   
                    .scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)   
                    .collect{|x,y| y || x.gsub(/(.)/, '\1')}    
# キー項目が単独のとき  
        in1_key =   in1[0]  
# キー項目が複数のとき  
# 1番目と2番目と3番目の項目がキーとなる場合  
#       in1_key =   in1[0] + in1[1] + in1[2]    
# マスターデータを格納した配列をハッシュに入れる    
        data1[in1_key]  =   in1 
end 

# トランザクションファイルの入力    
while   (line2  =   in2_file.gets)  
        line2.chomp!    
# タブ区切りのとき  
#       in2     =   line2.split("\t",-1)    
# カンマ区切りのとき    
        in2     =   (line2 + ',')   
                    .scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)   
                    .collect{|x,y| y || x.gsub(/(.)/, '\1')}    
# キー項目が単独のとき  
        in2_key =   in2[0]  
# キー項目が複数のとき  
# 1番目と2番目と3番目の項目がキーとなる場合  
#       in2_key =   in2[0] + in2[1] + in2[2]    
# トランザクションデータを格納した配列をハッシュに入れる    
        data2[in2_key]  =   in2 
end 

#マスターのデータを優先させる場合   
#data1.sort_by{|key| key }.each{|key,value| 
#       out1[key]   =   data1[key]  
#}  

#data2.sort_by{|key| key }.each{|key,value| 
# ハッシュのキーが存在しない場合(トランザクションにしかないとき)    
#   unless  (out1.include?(key))    
#       out1[key]   =   data2[key]  
#   end 
#}  

#トランザクションのデータを優先させる場合   
data2.sort_by{|key| key }.each{|key,value|  
        out1[key]   =   data2[key]  
}   

data1.sort_by{|key| key }.each{|key,value|  
#ハッシュのキーが存在しない場合(マスタにしかないとき)   
    unless  (out1.include?(key))    
        out1[key]   =   data1[key]  
    end 
}   

# マージしたデータを出力する    
out1.sort_by{|key| key }.each{|key,value|   
    w_temp  =   value.join("\t")    
    out1_file.print "#{w_temp}\n"   
}       

# ファイルのクローズ    
in1_file.close  
in2_file.close  
out1_file.close 
    
【スクリプトとデータのサンプル】

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

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

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

【スクリプトの解説】

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

マスタファイルの入力

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

# マスターファイルの入力    
while   (line1  =   in1_file.gets)  
        line1.chomp!    
# タブ区切りのとき  
#       in1     =   line1.split("\t",-1)    
# カンマ区切りのとき    
        in1     =   (line1 + ',')   
                    .scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)   
                    .collect{|x,y| y || x.gsub(/(.)/, '\1')}    
# キー項目が単独のとき  
        in1_key =   in1[0]  
# キー項目が複数のとき  
# 1番目と2番目と3番目の項目がキーとなる場合  
#       in1_key =   in1[0] + in1[1] + in1[2]    
# マスターデータを格納した配列をハッシュに入れる    
        data1[in1_key]  =   in1 
end 
    

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

    data1[in1_key]  =   in1 
   

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

    data1[in1_key]  =   [in1[1],in1[2]]
   

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

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

# トランザクションファイルの入力    
while   (line2  =   in2_file.gets)  
        line2.chomp!    
# タブ区切りのとき  
#       in2     =   line2.split("\t",-1)    
# カンマ区切りのとき    
        in2     =   (line2 + ',')   
                    .scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)   
                    .collect{|x,y| y || x.gsub(/(.)/, '\1')}    
# キー項目が単独のとき  
        in2_key =   in2[0]  
# キー項目が複数のとき  
# 1番目と2番目と3番目の項目がキーとなる場合  
#       in2_key =   in2[0] + in2[1] + in2[2]    
# トランザクションデータを格納した配列をハッシュに入れる    
        data2[in2_key]  =   in2 
end 
   

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

    data2[in2_key]  =   in2 
   

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

    data2[in2_key]  =   [in2[1],in2[2]]
   

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

次に入力した2つのデータを照合し、マスタファイルとトランザクションファイルの両方に存在する場合、どちらのデータを優先させるかによって、処理が変わります。

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

マスターのデータを優先させる場合   
data1.sort_by{|key| key }.each{|key,value| 
       out1[key]   =   data1[key]  
}  

data2.sort_by{|key| key }.each{|key,value| 
 ハッシュのキーが存在しない場合(トランザクションにしかないとき)    
   unless  (out1.include?(key))    
       out1[key]   =   data2[key]  
   end 
}  
   

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

#トランザクションのデータを優先させる場合   
data2.sort_by{|key| key }.each{|key,value|  
        out1[key]   =   data2[key]  
}   

data1.sort_by{|key| key }.each{|key,value|  
#ハッシュのキーが存在しない場合(マスタにしかないとき)   
    unless  (out1.include?(key))    
        out1[key]   =   data1[key]  
    end 
}   
   

出力処理

最後にマージしたハッシュからデータを出力します。

# マージしたデータを出力する    
out1.sort_by{|key| key }.each{|key,value|   
    w_temp  =   value.join("\t")    
    out1_file.print "#{w_temp}\n"   
}       
   


その他注意すべきこと

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

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

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

# カンマ区切りのとき    
        in1     =   (line1 + ',')   
                    .scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)   
                    .collect{|x,y| y || x.gsub(/(.)/, '\1')}    
   

上記のスクリプトはCSV2形式以外の引用符(")を使用する場合を含め、あらゆるCSVファイルに対応できるようになっています(CSV2形式などについては、[3-1-1.固定長データとCSVデータ]を参照してください)が、CSV2形式のCSVファイルであれば、上記のスクリプトは以下のように簡略化できます。

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

        in1 = line1.split(",",-1)
   

とします。

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

        in1 = line1.split("\t",-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) 2004-2015 Mitsuo Minagawa, All rights reserved.