1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Ruby】sortとsort_byの違い

Last updated at Posted at 2024-11-09

はじめに

こんにちは!アメリカの大学で語学を学びながら、独学でソフトウェアエンジニアを目指している者です。
本日はsortsort_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"]

sortsort_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とかなり違うことがわかります。

(補足)不安定なソートから安定なソートにするために

上記でも触れましたが、sortsort_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の sortsort_by はデフォルトでは不安定なソートであり、同じ値の要素があっても順序が保持される保証はありません。

不安定なソートに安定性を持たせる方法

Rubyの sortsort_by を安定なソートとして使用したい場合、インデックスを持たせる方法が効果的です。
インデックス(位置情報)を要素に付加し、それも比較の基準にすることで、同じ値の要素が出現したときに元の順序が保たれるようになります。

ary = ["apple1", "orange", "apple2", "banana"]

# インデックスを持たせて安定ソート
i = 0
sorted_ary = ary.sort_by { |v| [v, i += 1] }

p sorted_ary  # => ["apple1", "apple2", "banana", "orange"]

まとめ

sortsort_byについてみてきましたが、以下のようにまとめてみました

  • 要素が単純で比較が簡単
    sortを使うとコードが簡潔になります。
  • 複雑な評価が必要な場合
    sort_byを使うと、パフォーマンスが向上し効率的です。
  • 安定なソートにする場合
    インデックスを持たせてソートするのが望ましい

今回の記事はリファレンスマニュアルを参考に作ったので詳しく知りたい方は一読してみるといいかもしれません。

1
0
2

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?