8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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

参考

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?