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

■ナビゲータ

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

  1. [Windows版Rubyの細道・けもの道]
    1. [1.準備編]
    2. [2.基本編]
      1. [2-1.基本処理]
      2. [2-2.キーブレイク処理]
      3. [2-3.マッチング(照合)処理]
      4. [2-4.ソート(並べ替え)処理]
        1. [2-4-1.レコード全体のソート(並べ替え)]
        2. [2-4-2.CSVファイルの文字列型昇順ソート(並べ替え)]
        3. [2-4-3.CSVファイルの文字列型・数値型/昇順・降順ソート(その1)]
        4. [2-4-4.CSVファイルの文字列型・数値型/昇順・降順ソート(その2)]
        5. [2-4-5.大量のCSVファイルのソート(並べ替え)する場合の考慮事項]
        6. [2-4-6.大量のCSVファイルの文字列型/昇順ソート(並べ替え)]
        7. [2-4-7.大量のCSVファイルの文字列型・数値型/昇順・降順ソート(並べ替え)]
      5. [2-5.パターンマッチ処理]
    3. [3.応用編]
    4. [スクリプトと入力データのサンプル]
Perlではどう処理する?
同じことをPerlではこうしています。

2.基本編

2-4.ソート(並べ替え)処理

2-4-4.CSVファイルの文字列型・数値型/昇順・降順ソート(その2)

CSVファイルを文字列型や数値型に区別して、昇順にも降順にもソート(sort:並べ替え)することのできるスクリプトです。

数値型というのは、数字を計算するための数値として扱うということで、たとえば、昇順に並べ替えると「1,2,3,10,20,30」のような順番になりますが、文字列型では「1,10,2,20,3,30」のような順番になります。文字列型・数値型というのは、データそのものによって決まるのではなく、データをどのように扱う(認識する)かによって決まりますので、データが数字だからといって数値型と即断するのは誤りです(文字列型の可能性もあります)。Excelなどの表計算ソフトなどでは、この点を誤解しているようです。

並べ替える項目が何番目の項目かを指定し、その項目をどのように並べ替えるかを指定することによって、文字列型や数値型で昇順または降順で並べ替えをしていきます。並べ替えの方法は挿入ソートを元にしています。なお、実用的な入力データの件数は指定するキーの数にもよりますが、キーを2つ指定したときで、数千件程度までです。それ以上の入力件数がある場合は、実用にはなりません(ソート(sort:並べ替え)キーを増やせば、さらに入力件数の限度は少なくなります)。

実際に応用する場合は、入出力データのファイル名を変更し、「sort_item = [1,0]」の部分を変更します。この例では、2番目の項目を第1キーに、1番目の項目を第2キーとして並べ替えるように指定しています。Rubyでは、配列のインデックスは「0」から開始されますので、1番目の項目を「0」、2番目の項目を「1」として指定することに注意してください。

また、並べ替えの方法は「sort_type = ["NA","CA"]」の部分を変更します。この例では、2番目の項目を数値型の昇順に、1番目の項目を文字列型の昇順で並べ替えるように指定しています。各項目は「sort_item = [1,0]」で指定したものと対応させます。数字のない項目や「カンマつきの数字」、「通貨記号付きの数字」は数値型とはみなされませんので、注意してください。

下記の例では、フィールドを「"」(ダブルクォーテーションつき)で囲み、それぞれのデータを「,」(カンマ)で区切っている形式を前提としているため、

    in1 =   (record + ',')  
            .scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)   
            .collect{|x,y| y || x.gsub(/(.)/, '\1')}    
   

としていますが、フィールドを「"」(ダブルクォーテーションつき)で囲まずに、それぞれのデータを「,」(カンマ)で区切っている、いわゆる「CSV2形式」であれば、以下のようにすることができます。

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

また、タブ区切りであれば、以下のようにすることができます。

   in1 =   record.split("\t",-1)   
   
【スクリプト】
# csvsort3.rb   
# 内容 : 対象ファイルをCSV形式としてソートする 
#     文字列型の昇順・降順、数値型の昇順・降順の並べ替えを行う。    
#     (通貨記号や","つきの数字は数値型としての並べ替えはできない)   
# Copyright (c) 2009-2015 Mitsuo Minagawa, All rights reserved. 
# (minagawa@fb3.so-net.ne.jp)   
# 使用方法 : c:\>ruby csvsort3.rb   
#   
# 入力ファイルを全部読み込む。  
in1_file    =   IO.readlines("input.txt")   
# 出力ファイル  
out1_file   =   open("output.txt","w")  

# 整列の基準となる項目番号      
# ソートキー(2番目と1番目の項目)    
sort_item   =   [1,0]   
# ソートキーの入っている項目のデータ構造と昇順か降順かの指定    
# 文字列型:C 数値型:N/昇順:A 降順:D と指定する。 
# 例:文字列型の降順:CD、数値型の昇順:NA、数値型の降順:ND    
sort_type   =   ["NA","CA"] 

in1_ctr =   0       # 入力件数  
in1     =   Array.new   # 入力データを格納する配列  
in1_rec =   Array.new   # 並べ替えを行う配列    

w_sort_item =   Array.new   
w_sort_type =   Array.new   

# 入力ファイルのレコードセット  

in1_file.each   {|record|       #各行のレコードがrecordである。 
    #   カンマで各項目に分解する    
    record.chomp!   
    in1 =   (record + ',')  
            .scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)   
            .collect{|x,y| y || x.gsub(/(.)/, '\1')}    
    #   タブ区切りで結合する    
    in1_rec[in1_ctr]    =   in1.join("\t")  
    in1_ctr +=  1   
}   

# ソート項目の分だけ繰り返し    
# 並べ替え処理(挿入ソート)  

    w_sort_type =   sort_type.reverse   
    w_sort_item =   sort_item.reverse   
    for k   in  (0...w_sort_type.size)  do  

        n       =   w_sort_item[k]  

        if      (w_sort_type[k]     ==  "CA")   then        #文字列型の昇順 

            for i   in  (0...in1_rec.size)  do  
                for j   in  (1...in1_rec.size - i)  do  
                    #逆順の時   
                    if  (in1_rec[j - 1].split("\t")[n]      >   in1_rec[j].split("\t")[n])  
                        in1_rec[j - 1],in1_rec[j]   =   in1_rec[j],in1_rec[j - 1]   
                    end 
                end 
            end 
        elsif   (w_sort_type[k]     ==  "CD")   then        #文字列型の降順 

            for i   in  (0...in1_rec.size)  do  
                for j   in  (1...in1_rec.size - i)  do  
                    #逆順の時   
                    if  (in1_rec[j - 1].split("\t")[n]      <   in1_rec[j].split("\t")[n])  
                        in1_rec[j - 1],in1_rec[j]   =   in1_rec[j],in1_rec[j - 1]   
                    end 
                end 
            end 
        elsif   (w_sort_type[k]     ==  "NA")   then        #数値型の昇順   

            for i   in  (0...in1_rec.size)  do  
                for j   in  (1...in1_rec.size - i)  do  
                    #逆順の時   
                    if  (in1_rec[j - 1].split("\t")[n].to_f >   in1_rec[j].split("\t")[n].to_f) 
                        in1_rec[j - 1],in1_rec[j]   =   in1_rec[j],in1_rec[j - 1]   
                    end 
                end 
            end 
        elsif   (w_sort_type[k]     ==  "ND")   then        #数値型の降順   

            for i   in  (0...in1_rec.size)  do  
                for j   in  (1...in1_rec.size - i)  do  
                    #逆順の時   
                    if  (in1_rec[j - 1].split("\t")[n].to_f <   in1_rec[j].split("\t")[n].to_f) 
                        in1_rec[j - 1],in1_rec[j]   =   in1_rec[j],in1_rec[j - 1]   
                    end 
                end 
            end 
        end 
    end 

# ソート済ファイルの出力    
in1_rec.each    {|record|       #各行のレコードがrecordである。 
        out1_file.print record,"\n" 
}   

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

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

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

【スクリプトの解説】

それでは、1つずつ解説していきましょう。

ソート(並べ替え)キーのセット

    w_sort_type =   sort_type.reverse   
    w_sort_item =   sort_item.reverse   
   

「sort_type」と「sort_item」に入っている項目を逆順にします。入力データ全体をその都度並べ替えるので、これは優先度の低い項目から並べ替えをする必要があるためです。

ソート(並べ替え)キーの数だけ繰り返す

    for k   in  (0...w_sort_type.size)  do  

        n       =   w_sort_item[k]  
   

並べ替えを行うために繰り返し処理を行います。

最初のfor文は「w_sort_type」に指定した分だけ、並べ替えを繰り返すためのものです。0番目から「w_sort_type」の要素数から1を引いた数までを繰り返しますので、範囲演算子が「...」となっていることに注意してください。したがって、「w_sort_type」に3つの要素がある場合は、0から2まで繰り返すことになります。

また、並べ替えをする項目が何番目の項目かを「n」に入れます。

ソート(並べ替え)処理1

        if      (w_sort_type[k]     ==  "CA")   then        #文字列型の昇順 

            for i   in  (0...in1_rec.size)  do  
                for j   in  (1...in1_rec.size - i)  do  
                    #逆順の時   
                    if  (in1_rec[j - 1].split("\t")[n]      >   in1_rec[j].split("\t")[n])  
                        in1_rec[j - 1],in1_rec[j]           =   in1_rec[j],in1_rec[j - 1]   
                    end 
                end 
            end 
   

その後に、並べ替えのタイプ別に挿入ソートのロジックで並べ替え処理を行います。挿入ソートですので、同じキーであれば入力順を保つことができます。2つのfor文で、たとえば、CSVファイルの行数が10行の場合、まず、1行目から10行目までを調べて、その中の最大値のある行を10行目に入れ、次に1行目から9行目までを調べてその中で最大値のある行を9行目に入れ・・・ということを繰り返して行きます。2番目のfor文は10行目、9行目・・・と変化していく最大値を入れる行をコントロールしています。

そして、「in1_rec[j - 1]」と「in1_rec[j]」のソート(並べ替え)キーを比較し、ソート(並べ替え)キーが逆順の場合だけレコード全体を入れ替えます。

  in1_rec[j - 1],in1_rec[j]   =   in1_rec[j],in1_rec[j - 1]
   

上記のようにすることで、1行で隣り合ったレコードの入れ替えを行うことができます。

ソート(並べ替え)処理2

        elsif   (w_sort_type[k]     ==  "CD")   then        #文字列型の降順 

            for i   in  (0...in1_rec.size)  do  
                for j   in  (1...in1_rec.size - i)  do  
                    #逆順の時   
                    if  (in1_rec[j - 1].split("\t")[n]      <   in1_rec[j].split("\t")[n])  
                        in1_rec[j - 1],in1_rec[j]           =   in1_rec[j],in1_rec[j - 1]   
                    end 
                end 
            end 
        elsif   (w_sort_type[k]     ==  "NA")   then        #数値型の昇順   

            for i   in  (0...in1_rec.size)  do  
                for j   in  (1...in1_rec.size - i)  do  
                    #逆順の時   
                    if  (in1_rec[j - 1].split("\t")[n].to_f >   in1_rec[j].split("\t")[n].to_f) 
                        in1_rec[j - 1],in1_rec[j]           =   in1_rec[j],in1_rec[j - 1]   
                    end 
                end 
            end 
        elsif   (w_sort_type[k]     ==  "ND")   then        #数値型の降順   

            for i   in  (0...in1_rec.size)  do  
                for j   in  (1...in1_rec.size - i)  do  
                    #逆順の時   
                    if  (in1_rec[j - 1].split("\t")[n].to_f <   in1_rec[j].split("\t")[n].to_f)     
                        in1_rec[j - 1],in1_rec[j]           =   in1_rec[j],in1_rec[j - 1]   
                    end 
                end 
            end 
        end 
    end 
   

文字列型の降順や数値型の昇順・降順についても同様な方法で並べ替えをしていきます。逆順になったときだけ各レコード単位で入れ替えを行います。rubyでは、「a,b=b,a」とすることで「a」と「b」の内容を入れ替えることができます。

ソート(並べ替え)済ファイルの出力

# ソート(並べ替え)済ファイルの出力    
in1_rec.each    {|record|       #各行のレコードがrecordである。 
        out1_file.print record,"\n" 
}   
   

最後にソート(並べ替え)済みのファイルを出力します。

【入力データ:ソート(並べ替え)前のデータ(input.txt)】
ccc,5555   
aaa,7777
bbb,9999
aaa,1111
bbb,3333
bbb,4444
aaa,5555
ccc,1111
    
【出力データ:ソート(並べ替え)後のデータ(output.txt)】
aaa,1111   
ccc,1111
bbb,3333
bbb,4444
aaa,5555
ccc,5555
aaa,7777
bbb,9999
    



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