はじめに
Rubyで配列要素を並び替える際に使うsort
/sort_by
メソッド。
名前も役割も似ているメソッドですが、その呼び出し方や内部挙動は大きく異なります。
この記事では2つのメソッドの違いをまとめました。
※ サンプルコードの実行環境
$ ruby --version
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-darwin21]
Enumerable#sort
全要素を昇順ソートした配列を生成して返します。
ブロックがない場合は、要素そのものを<=>
で比較してソートします。
ary = (1..10).to_a.shuffle
# => [3, 4, 1, 8, 10, 9, 6, 2, 7, 5]
ary.sort
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ブロックが渡された場合は、ブロックの評価結果を用いてソートします。
ary = [{:id=>1, :name=>"Taro"}, {:id=>2, :name=>"Hanako"}, {:id=>3, :name=>"Ichiro"}]
ary.sort { |a, b| a[:name] <=> b[:name] }
# => [{:id=>2, :name=>"Hanako"}, {:id=>3, :name=>"Ichiro"}, {:id=>1, :name=>"Taro"}]
# 降順ソート
ary.sort { |a, b| b[:name] <=> a[:name] }
# => [{:id=>1, :name=>"Taro"}, {:id=>3, :name=>"Ichiro"}, {:id=>2, :name=>"Hanako"}]
Enumerable#sort_by
ブロックの評価結果を<=>
で比較して、全要素を昇順ソートした配列を生成して返します。
ary = [{:id=>1, :name=>"Taro"}, {:id=>2, :name=>"Hanako"}, {:id=>3, :name=>"Ichiro"}]
ary.sort_by { |user| user[:name] }
# => [{:id=>2, :name=>"Hanako"}, {:id=>3, :name=>"Ichiro"}, {:id=>1, :name=>"Taro"}]
# 数値による降順ソートは負の数を使えば可能
ary.sort_by { |user| -(user[:id]) }
# => [{:id=>3, :name=>"Ichiro"}, {:id=>2, :name=>"Hanako"}, {:id=>1, :name=>"Taro"}]
# 文字列による降順ソートは「Array#reverse」を使うなどの工夫が必要
ary.sort_by { |user| user[:name] }.reverse
# => [{:id=>1, :name=>"Taro"}, {:id=>3, :name=>"Ichiro"}, {:id=>2, :name=>"Hanako"}]
ブロックがない場合はEnumeratorを返します。
内部挙動の比較
sort
とsort_by
ではブロック評価の呼び出し回数が異なります。
Enumerable#sort_by (Ruby 3.1 リファレンスマニュアル)にもあるように、ブロック評価が行われる度にグローバル変数$n
を増やす方法で回数を確認してみます。
class Integer
def count
$n += 1
self
end
end
# 並び替えを行う配列
ary = (1..10000).to_a.shuffle
Enumerable#sort
の場合
$n = 0
ary.sort { |a, b| a.count <=> b.count }
# => [1, 2, ..., 10000]
$n
# => 273696
sort
では要素比較する度にブロック評価が行われます。
Enumerable#sort_by
の場合
$n = 0
ary.sort_by { |i| i.count }
# => [1, 2, ..., 10000]
$n
# => 10000
sort_by
でブロック評価が行われる回数は、配列の要素数と等しくなります。
したがって実行パフォーマンスの観点から、ブロックを渡して呼び出す場合はsort_by
を使った方が良いとわかります。
まとめ
- 配列要素そのものでソートする場合、
sort
を使えばブロックを渡さずに書ける - ブロックを渡すなら
sort_by
を使う方が良い-
sort
では要素を比較する度にブロック評価が行われる -
sort_by
では各要素に対して1回ずつブロック評価が行われる
-
つぶやき
この記事を書く中で、<=>
には「三方比較演算子」以外に「宇宙船演算子」という呼び方があることを知りました。
みなさん<=>
をどうやって呼んでいるのでしょうか。
(wikipediaのページタイトルを踏まえると、もしかしたら「宇宙船演算子」の方が一般的な呼び方なのかも…?)