👌

【Ruby】sortとsort_byってどう違う?

2023/02/28に公開

はじめに

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を返します。

内部挙動の比較

sortsort_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のページタイトルを踏まえると、もしかしたら「宇宙船演算子」の方が一般的な呼び方なのかも…?)

参考

Discussion