2つのCSVファイルを読み込んで、ハッシュを使って、同一のキー項目を元に照合を行うスクリプトです。
「1対nマッチング(照合)」は、「トランザクションファイル」において、同一のキーを持つレコードが複数存在する場合のマッチング(照合)処理として[2-3-4.1対nマッチング(照合)]で取り上げましたが、同様の処理についてハッシュを使って行う方法を以下に挙げます。[2-3-4.1対nマッチング(照合)]と異なる点は、入力データがキー順に並べ替えてある必要がない点です。
下記の例では「マッチング(照合)した場合」と「マスタオンリーの場合」を同じファイルに出力し、「トランザクションオンリーの場合」をエラーとして出力していますが、こうした分け方は、必要に応じて変更します。
# matching2_hash.rb # 内容 : マッチングプログラム(2ファイルの1対nマッチングプログラム) # 前提 : マスタファイルとトランザクションファイルの両方とも # マージするキーごとに昇順に並べ替えておく必要はない # Copyright (c) 2012-2015 Mitsuo Minagawa, All rights reserved. # (minagawa@fb3.so-net.ne.jp) # 使用方法 : c:\>ruby matching2_hash.rb # # ファイルのオープン in1_file = open("master.txt","r") #マスターファイル in2_file = open("transaction.txt","r") #トランザクションファイル out1_file = open("matching.txt","w") #マッチングファイル out2_file = open("masteronly.txt","w") #マスターオンリーファイル out3_file = open("tranonly.txt","w") #トランザクションオンリーファイル # 集計結果を入れるハッシュ data1 = Hash.new # エラー出力用ハッシュ data2 = Hash.new # 合計用の配列 total = Array.new # トランザクションファイルの件数 out2_ctr = 0 # マスタデータの入力 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] # ハッシュに初期値0の配列を代入する data1[in1_key] = [0,0] 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] out2_ctr += 1 w_key = in2_key + sprintf("%08d",out2_ctr) # トランザクションデータのキーがマッチングした場合、数値を加算する if (data1.include?(in2_key)) total = data1[in2_key] total[0] += in2[1].to_i total[1] += in2[2].to_i data1[in2_key] = total # 集計する項目が1つの場合 # data1[in2_key] += in2[1].to_i # ハッシュのキーが存在しない場合 else data2[w_key] = in2 end end # マッチングファイルを出力する data1.sort_by{|key| key }.each{|key,value| w_temp = [key,value].join("\t") if value != [0,0] out1_file.print "#{w_temp}\n" end } # マスターオンリーファイルを出力する data1.sort_by{|key| key }.each{|key,value| w_temp = [key,value].join("\t") if value == [0,0] out2_file.print "#{w_temp}\n" end } # トランザクションオンリーファイルを出力する data2.sort_by{|key| key }.each{|key,value| w_temp = [value].join("\t") out3_file.print "#{w_temp}\n" } # ファイルのクローズ in1_file.close in2_file.close out1_file.close out2_file.close out3_file.close
それでは、スクリプトの解説を個別に行っていきましょう。
マスタファイルの入力
ここではマスタファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目をハッシュのキーとし件数を合計するための初期値として0「ゼロ」をハッシュの値としてセットしています。なお、キーとなる項目は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] # ハッシュに初期値0の配列を代入する data1[in1_key] = [0,0] end
ハッシュに初期値0の配列を代入する場合、上記では
# ハッシュに初期値0の配列を代入する data1[in1_key] = [0,0]
# ハッシュに初期値0の配列を代入する data1[in1_key] = Array.new(2,0)
トランザクションファイルの入力
次にトランザクションファイルを入力し、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] out2_ctr += 1 w_key = in2_key + sprintf("%08d",out2_ctr) # トランザクションデータのキーがマッチングした場合、数値を加算する if (data1.include?(in2_key)) total = data1[in2_key] total[0] += in2[1].to_i total[1] += in2[2].to_i data1[in2_key] = total # 集計する項目が1つの場合 # data1[in2_key] += in2[1].to_i # ハッシュのキーが存在しない場合 else data2[w_key] = in2 end end
ここでは複数の項目を集計していますので、ハッシュを一度配列(total)にセットしてから、配列の各項目に数値を加算した後、配列を元のハッシュに代入して戻しています。集計する項目が1つしかない場合は、直接ハッシュの項目に対して、加算していきます。
合計値の出力
最後にトランザクションファイルの数値合計を集計したハッシュからデータを出力します。
# マッチングファイルを出力する data1.sort_by{|key| key }.each{|key,value| w_temp = [key,value].join("\t") if value != [0,0] out1_file.print "#{w_temp}\n" end }
マッチしないエラーデータの出力
また、マスターオンリーファイルとトランザクションオンリーファイルを出力します。ここでは、マスターオンリーファイルを別にしていますが、処理によっては、マッチングファイルと同じ出力先にする場合もあります。
# マスターオンリーファイルを出力する data1.sort_by{|key| key }.each{|key,value| w_temp = [key,value].join("\t") if value == [0,0] out2_file.print "#{w_temp}\n" end } # トランザクションオンリーファイルを出力する data2.sort_by{|key| key }.each{|key,value| w_temp = [value].join("\t") out3_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]
のように変更します(文字列の連結は「+」を使います)。
01,11111 02,22222 03,33333 05,44444 07,77777 08,88888 09,99999
01,100,1100 01,200,1200 01,300,1300 02,200,1200 04,400,1400 04,500,1500 04,600,1600 04,700,1700 06,600,1600 06,700,1700 07,700,1700 08,800,1800 09,900,1900 09,1000,2000 09,1100,2100 10,1200,2200
01 600 3600 02 200 1200 07 700 1700 08 800 1800 09 3000 6000
03 0 05 0
04 400 1400 04 500 1500 04 600 1600 04 700 1700 06 600 1600 06 700 1700 10 1200 2200