Rubyにおける Enumerable#sort と Enumerable#sort_by
Enumerable#sort について
ブロックなしの場合
sort -> [object]
各要素に対して <=> メソッドを実行し、その評価結果にもとづいてソートする
code:rb
# 文字列の昇順にならぶ
ブロックありの場合
sort {|a, b| ... } -> [object]
ブロックの評価結果にもとづいてソートする
ブロックの評価結果は次のいずれかの値が返ることを期待されている
a > b のとき: 正の整数
a == b のとき: 0
a < b のとき: 負の整数
code:rb
# 整数に変換した値にもとづいて昇順に並ぶ
# 今回の例では to_i の引き算でも同じ結果が得られる
これ調べてるんだけど、TypeErrorじゃなくてArgumentErrorが発生しない?
code:rb
# => `sort': comparison of Integer with 2 failed (ArgumentError)
# => `>': comparison of String with 0 failed (ArgumentError)
調査記録
どこかのバージョンからTypeErrorが出なくなってる?という仮説を立てた
手元のRepositoryで調べてみたけど、v1.9.3 ですでに ArgumentError が出ている
code:rb
begin
rescue => e
p e.class
end
このスクリプトを [1.9.3, 2.0.0, 2.7, 3.0, 3.3] この5つのバージョンで実行した
検証するスクリプトの実装に誤りがあるのかなぁ?
ruremaにissue立ててみようかしらね
Enumerable#sort_by について
ブロックなしの場合
sort_by -> Enumerator
Enumeratorオブジェクトを返すことの直接的なメリットはたぶんない、とおもう
Enumeratorオブジェクトのメソッドをメソッドチェーンできるから柔軟性が高いね、とかかな
Enumerator を生成するには Enumerator.newあるいは Object#to_enum, Object#enum_for を利用します。また、一部のイテレータはブロックを渡さずに呼び出すと繰り返しを実行する代わりに enumerator を生成して返します。
この "一部のイテレータは" に該当するかな?
ブロックありの場合
sort_by {|item| ... } -> [object]
ブロックの評価結果を <=> メソッドで比較することで、self を昇順にソートする
code:rb
# => ["1", "10", "11", "100"
共通点
wip: なんとかして不安定なソート結果になる例をつくる
相違点
wip
sort_by は sort よりも速度面で有利である
code:rb
numbers = Array.new(100) { rand(100).to_s }
# sort
count = 0
numbers.sort do |a, b|
count += 1
a.to_i <=> b.to_i
end
p count # => 564
# sort_by
count = 0
numbers.sort_by do |item|
count += 1
item.to_i
end
p count # => 100
sort のほうが柔軟性が高いと言えそう(私見)
ソート使われる値(正の整数、0、負の整数、のいずれを返すか)に直接的関与できるため
code:rb
# 100 だけ特別扱いして先頭に持ってくる
if a == '100'
-1
elsif b == '100'
1
else
a.to_i <=> b.to_i
end
end
逆に sort_by はブロックの評価値に対して <=> メソッドが呼ばれるので
評価結果のオブジェクトに実装された <=> メソッドに依存する
たとえば Object#<=> は 0 または nil を返す
nil を返した場合はエラーになる
評価結果のオブジェクトに <=> メソッドが実装されていない場合はNoMethodErrorになる
code:rb
class C
undef <=>
end
c1 = C.new
c2 = C.new