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" }
最後にソート(並べ替え)済みのファイルを出力します。
ccc,5555 aaa,7777 bbb,9999 aaa,1111 bbb,3333 bbb,4444 aaa,5555 ccc,1111
aaa,1111 ccc,1111 bbb,3333 bbb,4444 aaa,5555 ccc,5555 aaa,7777 bbb,9999