Array#sort ってややこしいですよね、イメージがわかないというか。(私だけ?) ということで、ちょこっと触って、それなりに理解できたのでメモ。 大まかにいうと、a <=> b とした値が-1なら a を先に、1ならbを先にするということらしいです。 以下、調査した時のスクリプト
sort.rb
# coding : windows-31j
# 0 から 1000 までのランダムな整数を10個もつ配列を生成
arr = 10.times.collect do |i|
rand(1000)
end
puts "Random Integers"
p arr
# Array#sort は
# 要素1 <=> 要素2 の結果が
# -1 なら 要素1が先
# 0 なら 同じ
# 1 なら 要素2が先
# となる。
# <=> は オブジェクトによって再定義可能なメソッドであるため
# -1, 0, 1 を返す条件はオブジェクトに依存するが、基本的には
# 要素1が大きければ1 等しければ0, 要素2が大きければ-1を
# 返すように作ることが期待されている。
# 昇順ソート
asc_arr = arr.sort do |a, b|
a <=> b
end
puts "Ascending Sort"
p asc_arr
# 降順ソート
# -1 : bを先にaを後にする
# 0 : 交換しない
# 1 : aを先にbを後にする
desc_arr = arr.sort do |a, b|
b <=> a
end
puts "Descending Sort"
p desc_arr
# ブロックを渡さずとも、デフォルトで昇順ソートになる。
puts "Default Sort(Ascending Sort)"
p (arr.sort)
# ブロックを渡すと便利な場合として、テーブルのソートがある
# カラムにID番号を整数で格納するid と 何かの名前を文字列で格納するnameを持った
# クラスが配列にランダムに格納されていたとする。
class AnyRecord
attr_accessor :id, :name
def initialize
@id = create_id
@name = create_name
end
def create_id
rand(1000)
end
def create_name
# ランダムな文字列生成
a = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
(
Array.new(16) do
a[rand(a.size)]
end
).join
end
def to_s
"id:#{@id} name:#{@name}"
end
end
table = 10.times.map do |i|
AnyRecord.new
end
puts "Random Records"
table.each { |r| p r }
# ID で昇順ソート
sorted_by_id_table = table.sort do |a, b|
a.id <=> b.id
end
puts "Sorted by id Records"
sorted_by_id_table.each { |r| p r }
# name で昇順ソート
sorted_by_name_table = table.sort do |a, b|
a.name <=> b.name
end
puts "Sorted by name Records"
sorted_by_name_table.each { |r| p r }
# ちなみに、引数なしでソートしようとするとエラーになる
# table.sort #=> comparison of Any Record with AnyRecord faild (ArgumentError)
# これはAnyRecord に 比較演算子 <=> が定義されておらず、比較できない為に発生する
# AnyRecord に 比較演算子 <=> を定義してやることで問題は解決する。
class AnyRecord
# ここでは、idでのソートをデフォルトソート(ブロックなしソート)とする
def <=>(other)
self.id <=> other.id
end
end
sorted_by_default_table = table.sort
puts "Sorted by default Records"
sorted_by_default_table.each { |r| p r }
# 複数のキーでソートする場合は以下のようにする
# nonzero? で nil になると、次のor節が評価される.
puts "Sorted by multi keys"
table.sort { |a, b|
(a.id <=> b.id).nonzero? ||
(a.name <=> b.name)
}.each { |r| p r }