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

■ナビゲータ

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

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

2.基本編

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

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

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

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

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

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

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

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

    $line1  =~  s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; 
    @in1    =   map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_}   
                ($line1 =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g);    
   

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

    @in1    =   split(",",$line1,-1);
   

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

    @in1    =   split("\t",$line1,-1);
   


【スクリプト】
# csvsort3.pl       
# 内容 : 対象ファイルをCSV形式としてソート(並べ替え)する 
#     文字列型の昇順・降順、数値型の昇順・降順の並べ替えを行う。    
#     (通貨記号や","つきの数字は数値型としての並べ替えはできない)   
# Copyright (c) 2009 Mitsuo Minagawa, All rights reserved.
# (minagawa@fb3.so-net.ne.jp)          
# 使用方法 : c:\>perl csvsort3.pl   
#   
open(IN1,"input.txt");      
open(OUT1,">output_s.txt");     

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

$in1_ctr    =   0;  

# CSVファイルの入力   
while   ($line1 =   <IN1>)  {   

    chomp($line1);  
    $line1  =~  s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; 
    @in1    =   map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_}   
                ($line1 =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g);    

    $in1_rec[$in1_ctr]  =   join("\t",@in1);    
    $in1_ctr++; 

}   

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

    @w_sort_type    =   reverse(@sort_type);    
    @w_sort_item    =   reverse(@sort_item);    

    for     ($k =   0;  $k  <=  $#w_sort_type;  $k++)   {   

                $n          =   $w_sort_item[$k];   

        if      ($w_sort_type[$k]   eq  "CA")       {   #文字列型の昇順 

            for     ($i =   0;  $i  <=  $#in1_rec;  $i++)   {   
                for     ($j =   1;  $j  <=  ($#in1_rec - $i);   $j++)   {   

                    $in1_key_a  =    (split("\t",$in1_rec[$j - 1]))[$n];
                    $in1_key_b  =    (split("\t",$in1_rec[$j]))[$n];

                    if  ($in1_key_a  gt $in1_key_b)         {   #逆順の時
                        ($in1_rec[$j],$in1_rec[$j - 1]) 
                                         =   ($in1_rec[$j - 1],$in1_rec[$j]);
                    }   
                }   
            }   
        }   
        elsif   ($w_sort_type[$k]   eq  "CD")       {   #文字列型の降順 

            for     ($i =   0;  $i  <=  $#in1_rec;  $i++)   {   
                for     ($j =   1;  $j  <=  ($#in1_rec - $i);   $j++)   {   

                    $in1_key_a  =    (split("\t",$in1_rec[$j - 1]))[$n];
                    $in1_key_b  =    (split("\t",$in1_rec[$j]))[$n];

                    if  ($in1_key_a  lt  $in1_key_b)        {   #逆順の時
                        ($in1_rec[$j],$in1_rec[$j - 1]) 
                                         =   ($in1_rec[$j - 1],$in1_rec[$j]);
                    }   
                }   
            }   
        }   
        elsif   ($w_sort_type[$k]   eq  "NA")       {   #数値型の昇順   

            for     ($i =   0;  $i  <=  $#in1_rec;  $i++)   {   
                for     ($j =   1;  $j  <=  ($#in1_rec - $i);   $j++)   {   

                    $in1_key_a  =    (split("\t",$in1_rec[$j - 1]))[$n];
                    $in1_key_b  =    (split("\t",$in1_rec[$j]))[$n];

                    if  ($in1_key_a  >  $in1_key_b)         {   #逆順の時
                        ($in1_rec[$j],$in1_rec[$j - 1]) 
                                         =   ($in1_rec[$j - 1],$in1_rec[$j]);
                    }   
                }   
            }   
        }   
        elsif   ($w_sort_type[$k]   eq  "ND")       {   #数値型の降順   

            for     ($i =   0;  $i  <=  $#in1_rec;  $i++)   {   
                for     ($j =   1;  $j  <=  ($#in1_rec - $i);   $j++)   {   

                    $in1_key_a  =    (split("\t",$in1_rec[$j - 1]))[$n];
                    $in1_key_b  =    (split("\t",$in1_rec[$j]))[$n];

                    if  ($in1_key_a  <  $in1_key_b)         {   #逆順の時
                        ($in1_rec[$j],$in1_rec[$j - 1]) 
                                         =   ($in1_rec[$j - 1],$in1_rec[$j]);
                    }   
                }   
            }   
        }   
    }   

# ソート(並べ替え)済ファイルの出力    
foreach $record (@in1_rec)  {   
    print   OUT1    "$record\n";    
}   

close(IN1); 
close(OUT1);    

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

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

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

【スクリプトの解説】

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

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

   @w_sort_type    =   reverse(@sort_type);
   @w_sort_item    =   reverse(@sort_item);
   

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

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

    for     ($k =   0;  $k  <=  $#w_sort_type;  $k++)   {   

                $n          =   $w_sort_item[$k];   
   

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

最初のfor文は「@w_sort_type」に指定した分だけ、並べ替えを繰り返すためのものです。このため、0番目から「@w_sort_type」の最終要素順である「$#w_sort_type」までを繰り返すように指定します。たとえば、「@w_sort_type」に3つの要素がある場合は、「2」が入ります。

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

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

        if      ($w_sort_type[$k]   eq  "CA")       {   #文字列型の昇順 

            for     ($i =   0;  $i  <=  $#in1_rec;  $i++)   {   
                for     ($j =   1;  $j  <=  ($#in1_rec - $i);   $j++)   {   

                    $in1_key_a  =    (split("\t",$in1_rec[$j - 1]))[$n];
                    $in1_key_b  =    (split("\t",$in1_rec[$j]))[$n];

                    if  ($in1_key_a  gt $in1_key_b)         {   #逆順の時
                        ($in1_rec[$j],$in1_rec[$j - 1]) 
                                         =   ($in1_rec[$j - 1],$in1_rec[$j]);
                    }   
                }   
            }   
        }   
   

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

「$in1_key_a」と「$in1_key_b」には、隣り合ったレコードでの該当するソート(並べ替え)キーを格納し、ソート(並べ替え)キーが逆順の場合だけレコード全体を入れ替えます。

    ($in1_rec[$j],$in1_rec[$j - 1]) =   ($in1_rec[$j - 1],$in1_rec[$j]);
   

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

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

        elsif   ($w_sort_type[$k]   eq  "CD")       {   #文字列型の降順 

            for     ($i =   0;  $i  <=  $#in1_rec;  $i++)   {   
                for     ($j =   1;  $j  <=  ($#in1_rec - $i);   $j++)   {   

                    $in1_key_a  =    (split("\t",$in1_rec[$j - 1]))[$n];
                    $in1_key_b  =    (split("\t",$in1_rec[$j]))[$n];

                    if  ($in1_key_a  lt  $in1_key_b)        {   #逆順の時
                        ($in1_rec[$j],$in1_rec[$j - 1]) 
                                         =   ($in1_rec[$j - 1],$in1_rec[$j]);
                    }   
                }   
            }   
        }   
        elsif   ($w_sort_type[$k]   eq  "NA")       {   #数値型の昇順   

            for     ($i =   0;  $i  <=  $#in1_rec;  $i++)   {   
                for     ($j =   1;  $j  <=  ($#in1_rec - $i);   $j++)   {   

                    $in1_key_a  =    (split("\t",$in1_rec[$j - 1]))[$n];
                    $in1_key_b  =    (split("\t",$in1_rec[$j]))[$n];

                    if  ($in1_key_a  >  $in1_key_b)         {   #逆順の時
                        ($in1_rec[$j],$in1_rec[$j - 1]) 
                                         =   ($in1_rec[$j - 1],$in1_rec[$j]);
                    }   
                }   
            }   
        }   
        elsif   ($w_sort_type[$k]   eq  "ND")       {   #数値型の降順   

            for     ($i =   0;  $i  <=  $#in1_rec;  $i++)   {   
                for     ($j =   1;  $j  <=  ($#in1_rec - $i);   $j++)   {   

                    $in1_key_a  =    (split("\t",$in1_rec[$j - 1]))[$n];
                    $in1_key_b  =    (split("\t",$in1_rec[$j]))[$n];

                    if  ($in1_key_a  <  $in1_key_b)         {   #逆順の時
                        ($in1_rec[$j],$in1_rec[$j - 1]) 
                                         =   ($in1_rec[$j - 1],$in1_rec[$j]);
                    }   
                }   
            }   
        }   
    }   
   

文字列型の降順や数値型の昇順・降順についても同様な方法で並べ替えをしていきます。逆順になったときだけ各レコード単位で入れ替えを行います。

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

# ソート(並べ替え)済ファイルの出力    
foreach $record (@in1_rec)  {   
    print   OUT1    "$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-2013 Mitsuo Minagawa, All rights reserved.