ソート(並べ換えること)は、大量のデータ処理には欠かせない。
数値だけが並ぶ配列であればsort
メソッドでソートできるが、
複雑な配列やハッシュを様々な条件で並べ換えるときに有用な
sort_by
の利用方法に慣れておこう。
以下の説明では分かりやすくするため、 ソート対象となる配列(またはハッシュ)に初期値を代入しているが、 実践プログラムではプログラム起動時に 入力されたデータなどを元にその都度変わる。
たくさんの値が要素として配列に格納されている場合、それらを
昇順にソートしたものを得たいならたんにsort
メソッドを
呼ぶだけでよい。
x = [9, 20, 10, 15, 8] y = x.sort y => [8, 9, 10, 15, 20] x = ["orange", "apple", "strawberry", "pear"] y = x.sort y => ["apple", "orange", "pear", "strawberry"]
数値でも文字列でも昇順に並べ換えられる。降順にしたい場合は
結果を reverse
で反転すればよい。
x = [9, 20, 10, 15, 8] y = x.sort.reverse y => [20, 15, 10, 9, 8] foo = ["orange", "apple", "strawberry", "pear"] bar = foo.sort.reverse bar => ["strawberry", "pear", "orange", "apple"]
降順順、昇順以外の複雑な並べ換えもできる。 たとえば、次のようなCSVデータの並べ換えを考える。
氏名,国語,数学 山田太郎,50,40 中町太郎,90,70 飯森花子,91,90 鶴岡一人,60,50 酒田三吉,52,80 三川一二三,12,96
このCSVファイルを
score = CSV.read("ja-math.csv", headers:true)
として読み込むと概念的に以下のような構造で読み込まれる(実際には CSV::Tableの値だがハッシュの配列と同じ性質を持つ)。
score = [ # 内部にハッシュを複数持つ配列
{"氏名" => "山田太郎", "国語"=> 50, "数学"=>40},
{"氏名" => "中町太郎", "国語"=> 90, "数学"=>70},
{"氏名" => "飯森花子", "国語"=> 91, "数学"=>90},
{"氏名" => "鶴岡一人", "国語"=> 60, "数学"=>50},
{"氏名" => "酒田三吉", "国語"=> 52, "数学"=>80},
{"氏名" => "三川一二三", "国語"=> 12, "数学"=>96},
]
score.length # 要素数は6
=> 6
# たとえば添字番号2の要素は "飯森花子" 行の値となる
このようなときに「6つの要素を「国語」キーの値(つまり国語の点)の 高い順に並び換える」ことを考える。
普通の sort
メソッドは各要素どうしをそのまま直接比較して
ソートするが、要素そのままではなくハッシュのキーを取り出した「値」で
比べることを指定する。そのために使うのが sort_by
メソッドで、以下のように用いる。
配列.sort_by{|変数| 変数から比較すべきものを取り出す式}
score 変数の例の場合では、たとえば
x = score[2]
とした場合に、x から国語の点数を取り出すには、
x["国語"].to_i
とすればよいことから、
score.sort_by{|x| x["国語"].to_i}
とすることで国語の点数で昇順にした配列が得られ、さらにこれを
reverse
メソッドに渡して降順の結果が得られる。
score.sort_by{|x| x["国語"].to_i}.reverse
=>
[#<CSV::Row "氏名":"飯森花子" "国語":"91" "数学 ":"90">,
#<CSV::Row "氏名":"中町太郎" "国語":"90" "数学":"70">,
#<CSV::Row "氏名":"鶴岡一人" "国語":"60" "数学":"50">,
#<CSV::Row "氏名":"酒田三吉" "国語":"52" "数学":"80">,
#<CSV::Row "氏名":"山田太郎" "国語":"50" "数学":"40">,
#<CSV::Row "氏名":"三川一二三" "国語":"12" "数学":"96">]
このソート結果を順序よく出力するには、each
で1つずつ取り出して処理する。
score.sort_by{|x| x["国語"].to_i}.reverse.each do |i| ……出力処理…… end
以上をまとめたプログラムを示す。
#!/usr/bin/env ruby # coding: utf-8 require "./kprintf.rb" require 'csv' # ja-math.csv: 氏名,国語,数学 score = CSV.read("ja-math.csv", headers:true) puts "元の並び:" print "--氏名--------------+-国語-+-数学-+-合計--\n" score.each do |row| name, ja, math = row["氏名"], row["国語"], row["数学"] total = ja.to_i + math.to_i printf("%-20s %5d %5d %5d\n", name, ja.to_i, math.to_i, total) end puts "-"*42 puts "国語上位から:" print "--氏名--------------+-国語-+-数学-+-合計--\n" score.sort_by{|x| x["国語"].to_i}.reverse.each do |row| name, ja, math = row["氏名"], row["国語"], row["数学"] total = math.to_i + ja.to_i printf("%-20s %5d %5d %5d\n", name, math.to_i, ja.to_i, total) end puts "-"*42
以下の出力が得られる。
./kokugo-rank.rb
元の並び:
--氏名--------------+-国語-+-数学-+-合計--
山田太郎 50 40 90
中町太郎 90 70 160
飯森花子 91 90 181
鶴岡一人 60 50 110
酒田三吉 52 80 132
三川一二三 12 96 108
------------------------------------------
国語上位から:
--氏名--------------+-国語-+-数学-+-合計--
飯森花子 90 91 181
中町太郎 70 90 160
鶴岡一人 50 60 110
酒田三吉 80 52 132
山田太郎 40 50 90
三川一二三 96 12 108
------------------------------------------
※発展※ →ハッシュ値をまるごとソート