はじめに
こんにちは!アメリカの大学で語学を学びながら、独学でソフトウェアエンジニアを目指している者です。
本日はsort
とsort_by
の違いについて解説していきたいと思います。
Rubyを勉強しているうえでsortかsort_byどちらを使うかわからない方もいると思いますが、それぞれ使う場面やパフォーマンスが違います。
それぞれ見ていきましょう
sort
メソッドの特徴
sort
メソッドは、配列やリストのすべての要素を昇順に並べ替えた新しい配列を返します。
デフォルトでは <=> 演算子を使って比較を行いますが、ブロックを渡すことでカスタムのソート条件も指定できます。
sort
の特徴は以下の通りです
- デフォルトのソート順
<=> 演算子での比較に基づき、昇順にソートされます。 - カスタムのソート条件
ブロックを渡すと、そのブロックの評価結果に従ってソートされます。 - 安定性
sortメソッドは「安定なソート」ではないため、同じ値の要素の順序が保証されません。
numbers = [5, 3, 8, 1]
# 昇順ソート
sorted_numbers = numbers.sort # => [1, 3, 5, 8]
# 降順ソート(ブロックを使用)
sorted_desc = numbers.sort { |a, b| b <=> a } # => [8, 5, 3, 1]
sort_by
メソッドの特徴
sort_by
メソッドは、ブロック内で評価した結果を基に並べ替えを行うため、複雑な条件でのソートに適しています。
sort
と異なり、一度ブロックの処理を行ってから<=>を使って比較するため、複雑な評価が必要な場合にパフォーマンスが向上します。
sort_by
の特徴は以下の通りです。
- 評価結果を基にソート
各要素の評価結果(ブロックの返り値)を使ってソートされます。 - 高速
sort
と比較して、同じ要素の評価が1回しか行われないため、複雑な条件のソートにおいて効率的です。 - 安定性
sort
メソッド同様、「安定なソート」ではないため、同じ値の要素の順序が保証されません。
words = ["Banana", "apple", "cherry", "date"]
# 大文字・小文字を区別せずソート
sorted_words = words.sort_by { |word| word.downcase }
# => ["apple", "Banana", "cherry", "date"]
sort
とsort_by
のパフォーマンス比較
sort
メソッドでは、比較のたびにブロック内で評価を行う必要があるため、複雑な処理が繰り返し実行されてしまう場合があります。
一方、sort_by
は各要素を1度だけ評価し、その結果をもとにソートするため、効率が良いです。
以下の例で、評価回数を確認してみましょう。
class Integer
def count
$n += 1
self
end
end
# ランダムな数値の配列
ary = Array.new(1000) { rand(1000) }
$n = 0
ary.sort { |a, b| a.count <=> b.count }
puts $n # => 18200
$n = 0
ary.sort_by { |a| a.count }
puts $n # => 1000
Integer#count
メソッドが呼び出されたら$n
が1ずつ増え呼び出し回数が増える仕組みです。
sort
の場合は<=>
を使用してそれぞれを比較しているのでcount
メソッドは18200
とかなり多くなっていますが、sort_by
の場合はブロックで処理をしてから一度だけソートをしているので1000
とかなり違うことがわかります。
(補足)不安定なソートから安定なソートにするために
上記でも触れましたが、sort
とsort_by
メソッドは不安定なソートであるため、同じ値の要素の順序が変わる可能性があります。
この記事では、Rubyの不安定なソートに安定性を持たせる方法について詳しく解説します。
不安定なソートとは?
ソートには「安定」と「不安定」の2種類があります。
- 安定なソート
同じ値を持つ要素があった場合、元の順序が保持されるソート - 不安定なソート
同じ値を持つ要素があった場合、元の順序が保持されない可能性があるソート
例えば、次のように "apple" が複数ある配列を考えてみます。
fruits = ["apple1", "orange", "apple2", "banana"]
この配列をソートして、"apple"
同士が並ぶようにしたいとしましょう。
ただし、元の順序も気にしたい場合、次のような違いが生じます
安定なソートでは、同じ "apple"
であっても順序が変わらず、元の配列の順序が保たれたまま "apple1"
が先に、"apple2"
が後に並びます
["apple1", "apple2", "banana", "orange"]
不安定なソートでは、同じ "apple" 同士の元の順序は考慮されないので、"apple2"
が "apple1"
の前に並ぶ可能性があります
["apple2", "apple1", "banana", "orange"]
Rubyの sort
と sort_by
はデフォルトでは不安定なソートであり、同じ値の要素があっても順序が保持される保証はありません。
不安定なソートに安定性を持たせる方法
Rubyの sort
や sort_by
を安定なソートとして使用したい場合、インデックスを持たせる方法が効果的です。
インデックス(位置情報)を要素に付加し、それも比較の基準にすることで、同じ値の要素が出現したときに元の順序が保たれるようになります。
ary = ["apple1", "orange", "apple2", "banana"]
# インデックスを持たせて安定ソート
i = 0
sorted_ary = ary.sort_by { |v| [v, i += 1] }
p sorted_ary # => ["apple1", "apple2", "banana", "orange"]
まとめ
sort
とsort_by
についてみてきましたが、以下のようにまとめてみました
- 要素が単純で比較が簡単
sort
を使うとコードが簡潔になります。 - 複雑な評価が必要な場合
sort_by
を使うと、パフォーマンスが向上し効率的です。 - 安定なソートにする場合
インデックスを持たせてソートするのが望ましい
今回の記事はリファレンスマニュアルを参考に作ったので詳しく知りたい方は一読してみるといいかもしれません。