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

■ナビゲータ

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

  1. [Windows版Rubyの細道・けもの道]
    1. [1.準備編]
    2. [2.基本編]
      1. [2-1.基本処理]
      2. [2-2.キーブレイク処理]
        1. [2-2-1.各キーの1件目を出力(until型)]
        2. [2-2-2.各キーの最終レコードを出力(until型)]
        3. [2-2-3.同一キーでの合計処理(until型)]
        4. [2-2-4.同一キーでの件数カウント(until型)]
        5. [2-2-5.各キーの1件目を出力(while型)]
        6. [2-2-6.各キーの最終レコードを出力(while型)]
        7. [2-2-7.同一キーでの合計処理(while型)]
        8. [2-2-8.同一キーでの件数カウント(while型)]
        9. [2-2-9.ハッシュを使った同一キーの合計処理]
      3. [2-3.マッチング(照合)処理]
      4. [2-4.ソート(並べ替え)処理]
      5. [2-5.パターンマッチ処理]
    3. [3.応用編]
    4. [スクリプトと入力データのサンプル]
Perlではどう処理する?
同じことをPerlではこうしています。

2.基本編

2-2.キーブレイク処理

2-2-9.ハッシュを使った同一キーの合計処理

CSVファイルを読み込んで、同一キーのレコードの中の数値項目をハッシュを使って合計する処理です。

[2-2-3.同一キーでの合計処理(until型)][2-2-7.同一キーでの合計処理(while型)]ではuntilやwhileで繰り返しを行って、合計を計算するスクリプトについて解説しましたが、あらかじめキー順(昇順など)に並べ替えておく必要がありました。これに対し、ハッシュを使って行う合計処理では、あらかじめキー順に並べ替えて並べ替えておかなくても、合計処理ができるのが特徴です。

【スクリプト】
# total_hash.rb 
# 内容 : 同一キーの値を合計する 
# 前提 : 合計値を出すキーごとに昇順に並べ替えておく必要はない   
# Copyright (c) 2012-2015 Mitsuo Minagawa, All rights reserved. 
# (minagawa@fb3.so-net.ne.jp)   
# 使用方法 : c:\>ruby total_hash.rb 
#   

# 入力ファイル  
in1_file    =   open("input.txt","r")   
# 出力ファイル  
out1_file   =   open("output.txt","w")  
# 出力ファイル用ハッシュ    
out1        =   Hash.new    
# 合計用の配列  
w_total =   Array.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]    
# ハッシュのキーがすでに存在する場合    
    if  (out1.include?(in1_key))    
        w_total =   out1[in1_key]   
        w_total[0]  +=  in1[1].to_i 
        w_total[1]  +=  in1[3].to_i 
        out1[in1_key]   =   w_total 
# 集計する項目が1つの場合   
#       out1[in1_key]   +=  in1[1].to_i 
# ハッシュのキーが存在しない場合    
    else    
        w_total =   Array.new   
        w_total[0]  =   in1[1].to_i 
        w_total[1]  =   in1[3].to_i 
        out1[in1_key]   =   w_total 
# 集計する項目が1つの場合   
#       out1[in1_key]   =   in1[1].to_i 
    end 
end 

# ハッシュのキーをソートして出力する    
out1.sort_by{|key| key }.each{|key,value|   
    w_temp  =   [key,value].join("\t")  
    out1_file.print "#{w_temp}\n"   
}       

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

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

入力データのサンプルはこちらにあります。

【スクリプトの解説】

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

入力データの配列へのセット

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

in1_file    =   open("input.txt","r")   
while   (line1  =   in1_file.gets)  
        line1.chomp!    
# タブ区切りのとき  
#       in1 =   line1.split("\t")   
# カンマ区切りのとき    
        in1     =   (line1 + ',')   
                    .scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)   
                    .collect{|x,y| y || x.gsub(/(.)/, '\1')}    
        in1_key =   in1[0]  
# ハッシュのキーがすでに存在する場合    
    if  (out1.include?(in1_key))    
        total   =   out1[in1_key]   
        total[0]    +=  in1[1].to_i 
        total[1]    +=  in1[3].to_i 
        out1[in1_key]   =   total   
# 集計する項目が1つの場合   
#       out1[in1_key]   +=  in1[1].to_i 
# ハッシュのキーが存在しない場合    
    else    
        total       =   Array.new   
        total[0]    =   in1[1].to_i 
        total[1]    =   in1[3].to_i 
        out1[in1_key]   =   total   
# 集計する項目が1つの場合   
#       out1[in1_key]   =   in1[1].to_i 
    end 
end 
in1_file.close  
   

上記では、集計する項目をCSVファイルの2項目目(in1[1])と4項目目(in1[3])と想定していますが、集計する項目がそれ以上であれば、項目を追加していきます。また、集計する項目を整数であると想定しているため、「in1[1].to_i」のようにしていますが、実数を集計する場合には、「in1[1].to_f」とします。さらに、集計する項目が1つの場合は、下記の部分を

# ハッシュのキーがすでに存在する場合    
    if  (out1.include?(in1_key))    
        total   =   out1[in1_key]   
        total[0]    +=  in1[1].to_i 
        total[1]    +=  in1[3].to_i 
        out1[in1_key]   =   total   
# ハッシュのキーが存在しない場合    
    else    
        total       =   Array.new   
        total[0]    =   in1[1].to_i 
        total[1]    =   in1[3].to_i 
        out1[in1_key]   =   total   
    end 
end 
   

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

# ハッシュのキーがすでに存在する場合    
    if  (out1.include?(in1_key))    
# 集計する項目が1つの場合   
        out1[in1_key]   +=  in1[1].to_i 
# ハッシュのキーが存在しない場合    
    else    
# 集計する項目が1つの場合   
        out1[in1_key]   =   in1[1].to_i 
    end 
end 
   

ファイルの出力

最後に、ハッシュのキーをソート(並べ替え)して、出力します。

# ハッシュのキーをソートして出力する    
out1.sort_by{|key| key }.each{|key,value|   
    w_temp  =   [key,value].join("\t")  
    out1_file.print "#{w_temp}\n"   
}       
   

その他注意すべきこと

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

上記のスクリプトでは、入力ファイルがCSV形式であれば、すべて対応できるようになっていますが、引用符(")をつけないCSV形式(いわゆるCSV2形式と呼ばれるものです。CSV2形式については、[3-1-1.固定長データとCSVデータ]を参照してください)であれば、

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

上記の箇所を

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

と変更します。

また、入力ファイルがタブ区切りの場合は以下のようにします。

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

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

とします。

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

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

          in1_key = in1[0]+in1[1]+in1[2]
   

のように変更します(文字列の連結は「+」(プラス)を使います)。

【入力データ(input.txt)】
aaa,1   
aaa,2
aaa,3
bbb,1
ccc,1
ccc,2
ddd,1
ddd,2
ddd,3
ddd,4
eee,1
fff,1
ggg,1
hhh,1
hhh,2
hhh,3
   
【出力データ(output.txt)】
aaa,6   
bbb,1
ccc,3
ddd,10
eee,1
fff,1
ggg,1
hhh,6
   



Copyright (c) 2004-2015 Mitsuo Minagawa, All rights reserved.