【Ruby】sortメソッドのブロックの役割

調べたことのメモです📝

sortメソッド

ary = [2,4,8,1]

# デフォルトでsortメソッドを使う
p ary.sort # => [1, 2, 4, 8]

# こういう書き方もできる
p ary.sort{ |i,j| i <=> j } # => [1, 2, 4, 8]

# 降順もサッと書ける(以下どちらでも同じ)
p ary.sort{ |i,j| j <=> i } # => [8, 4, 2, 1]
p ary.sort{ |i,j| -i <=> -j } # => [8, 4, 2, 1]

# reverseで降順にもできるけど、sortとreverseで2回処理するので非効率
p ary.sort.reverse # => [8, 4, 2, 1]

ブロックなしの書き方は無意識に使っていたのですが、ブロックありで宇宙船演算子 <=> を使う時に何が起こっているのかイマイチわかっていなかったので確認しました。

公式リファレンス

docs.ruby-lang.org

全ての要素を昇順にソートします。要素同士の比較は <=> 演算子を使って行います。

(中略)

ブロックとともに呼び出された時には、要素同士の比較をブロックを用いて行います。ブロックに2つの要素を引数として与えて評価し、その結果で比較します。ブロックは <=> 演算子と同様に整数を返すことが期待されています。つまり、ブロックは第1引数が大きいなら正の整数、両者が等しいなら0、そして第1引数の方が小さいなら負の整数を返さなければいけません。両者を比較できない時は nil を返します。

うーん、これだけではよくワカラナイ😇

宇宙船演算子 <=> のおさらい

宇宙船演算子 <=> は、左辺と右辺を比較して、以下のように結果を返す。

1 <=> 10  # => -1(左辺が小さい)
10 <=> 10 # => 0(等しい)
10 <=> 1  # => 1(左辺が大きい)

sortのブロックは「比較のルール」を定義している

要は、 sort にブロックを渡すと、「2つの要素を比較してどちらを前にするか」というルールを指定できる、という感じで受け取りました。

ary.sort { |i, j| i <=> j }

ブロックの戻り値(-1, 0, 1)によって、sortが並び順を決めてくれる。

戻り値 意味
-1 i を前に i=2 , j=4 の場合、 i を前にしたまま、
つまり順番はそのまま
0 何もしない i=2, j=2 の場合、
何もしないので順番はそのまま
1 j を前に i=4, j=2 の場合、 j を前にする、
つまり ij を入れ替える

sort は内部でこのブロックを片っ端から何度も呼び出し、全体をどんどん並び替えていってくれる、というイメージ。

※実際のアルゴリズムとしてクイックソートアルゴリズムバブルソートアルゴリズムといった仕組みがある(そして sortクイックソートアルゴリズムが適用されているらしい)ことも知りましたが、難しかったのでここでは割愛します🙏

ただ、Wikipediaに載っているそれぞれのGIFアニメーションがわかりやすかったので、そのリンクは貼っておきます!

ja.wikipedia.org

ja.wikipedia.org

降順にするには

i <=> jj <=> i にするだけで、大小関係が逆転して降順になる。

# 昇順
ary.sort { |i, j| i <=> j } # => [1, 2, 4, 8]

# 降順(i と j を入れ替える)
ary.sort { |i, j| j <=> i } # => [8, 4, 2, 1]

# ちなみにこう書いても降順になる
ary.sort { |i, j| -i <=> -j } # => [8, 4, 2, 1]

おわりに

ary.sort{ |i,j| i <=> j }という1行に隠された動きの片鱗を知ることができて、良かったです。