様々な条件での並べ換え

ソート(並べ換えること)は、大量のデータ処理には欠かせない。 数値だけが並ぶ配列であれば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"]

独自規準のソート

降順順、昇順以外の複雑な並べ換えもできる。 たとえば、次のような配列の並べ換えを考える。

score = [			# 内部にハッシュを複数持つ配列
  {"氏名" => "山田太郎",	"国語"=> 50, "数学"=>40},
  {"氏名" => "中町太郎",	"国語"=> 90, "数学"=>70},
  {"氏名" => "飯森花子",	"国語"=> 91, "数学"=>90},
  {"氏名" => "鶴岡一人",	"国語"=> 60, "数学"=>50}
]
score.length			# 要素数は4
 => 4
# たとえば添字番号2の要素は以下のハッシュ
score[2]
 => {"氏名"=>"飯森花子", "国語"=>91, "数学"=>90}

このようなときに「4つの要素を「国語」キーの値(つまり国語の点)の 高い順に並び換える」ことを考える。

普通の sort メソッドは各要素どうしをそのまま直接比較して ソートするが、要素そのままではなくハッシュのキーを取り出した「値」で 比べることを指定する。そのために使うのが sort_by メソッドで、以下のように用いる。

配列.sort_by{|変数| 変数から比較すべきものを取り出す式}

score 変数の例の場合では、たとえば

x = score[2]

とした場合に、x から国語の点数を取り出すには、 x["国語"] とすればよいことから、

score.sort_by{|x| x["国語"]}

とすることで国語の点数で昇順にした配列が得られ、さらにこれを reverse メソッドに渡して降順の結果が得られる。

score.sort_by{|x| x["国語"]}.reverse
 => [{"氏名"=>"飯森花子", "国語"=>91, "数学"=>90},
     {"氏名"=>"中町太郎", "国語"=>90, "数学"=>70},
     {"氏名"=>"鶴岡一人", "国語"=>60, "数学"=>50},
     {"氏名"=>"山田太郎", "国語"=>50, "数学"=>40}]

このソート結果を順序よく出力するには、for 文で1つずつ取り出して処理する。

for i in score.sort_by{|x| x["国語"]}.reverse
  ……出力処理……
end

以上をまとめたプログラムを示す。

kokugo-rank.rb

#!/usr/koeki/bin/ruby
# coding: utf-8
require "./kprintf.rb"

point = Array.new

while yline = gets
  if /(\S+)\s+(\d+)\s+(\d+)/ =~ yline
    # 1個目の() (\S+)→氏名が入る
    # 2個目の() (\d+)→国語の得点が入る
    # 3個目の() (\d+)→数学の得点が入る
    j = $2.to_i			# 国語の得点
    m = $3.to_i			# 数学の得点
    point << {"name" => $1, "ja" => j, "math" => m, "total" => j+m}
    # ついでに合計点も入れておく
  end
end

print "--氏名--------------+-国語-+-数学-+-合計--\n"
for i in
  point.sort_by{|x| x["ja"]}.reverse
  student = i["name"]
  kokugo  = i["ja"]
  math    = i["math"]
  total   = i["total"]
  printf("%-20s %5d  %5d  %5d\n", student, kokugo, math, total)
end
puts "-"*42

以下の出力が得られる。

--氏名--------------+-国語-+-数学-+-合計--
飯森花子                91     90    181
中町太郎                90     70    160
鶴岡一人                60     50    110
酒田三吉                52     80    132
山田太郎                50     40     90
三川一二三              12     75     87
------------------------------------------

※発展※ →ハッシュ値をまるごとソート


本日の目次