複数キーのソート Enumerable#sort (昇順・降順のみ、昇順・降順混在)

すみません控えのみです。

まずsortメソッド、sort_byメソッドの復習

  • instance method Enumerable#sort

sort -> [object]
sort {|a, b| ... } -> [object]
全ての要素を昇順にソートした配列を生成して返します。
ブロックなしのときは <=> メソッドを要素に対して呼び、 その結果をもとにソートします。
<=> 以外でソートしたい場合は、ブロックを指定します。 この場合、ブロックの評価結果を元にソートします。 ブロックの値は、a > b のとき正、a == b のとき 0、 a < b のとき負の整数を、期待しています。 ブロックが整数以外を返したときは例外 TypeError が発生します。
Enumerable#sort_by は安定ではありません (unstable sort)。 安定なソートが必要な場合は Enumerable#sort_by を使って工夫する必要があります。 詳しくは Enumerable#sort_by の項目を参照してください。
※ 比較結果が同じ要素は元の順序通りに並ぶソートを 「安定なソート (stable sort)」と言います。
[SEE_ALSO] Enumerable#sort_by

http://rurema.clear-code.com/1.9.3/method/Enumerable/i/sort.html

  • instance method Enumerable#sort_by

sort_by -> Enumerator
sort_by {|item| ... } -> [object]
ブロックの評価結果を <=> メソッドで比較することで、self を昇順にソートします。ソートされた配列を新たに生成して返します。
つまり、以下とほぼ同じ動作をします。

class Array
  def sort_by
    self.map {|i| [yield(i), i] }.
       sort {|a, b| a[0] <=> b[0] }.
       map {|i| i[1]}
  end
end

Enumerable#sort と比較して sort_by が優れている点として、 比較条件が複雑な場合の速度が挙げられます。
Enumerable#sort_by は安定ではありません (unstable sort)。 ただし、sort_by を以下のように使うと安定なソートを実装できます。

i = 0
ary.sort_by {|v| [v, i += 1] }

※ 比較結果が同じ要素は元の順序通りに並ぶソートを 「安定なソート (stable sort)」と言います。
ブロックを省略した場合は、各要素をブロックで評価した値でソートした 配列を返すような Enumerator を返します。
[SEE_ALSO] Enumerable#sort

http://rurema.clear-code.com/1.9.3/method/Enumerable/i/sort_by.html

複数キーソート(昇順のみ)

Bookのauthorを第1キー、titleを第2キーとして昇順ソートする。
 :

books = books.sort_by do |b|
  [b.author, b.title]
end

複数キーソート(昇順・降順混在)

ところが、この方法だと降順でソートするのが難しい。
books.reverse!で、全体を逆順にすることはできる。
しかし、昇順降順が混在するようなソートはできない。

ブロックつきのsortメソッドを使えば、降順ソートも可能となる。
authorで昇順、titleで降順ソートするコードは下記のようになる。
 :

books.sort! do |a, b|
  (a.author <=> b.author).nonzero? ||
    (b.title <=> a.title)
end

nonzero?は、ゼロの時nilを返し、非ゼロの時 self を返す。
第1キー(author)が同じであれば、第2キー(title)を比較することになる。
The Art of Software : Rubyで複数キーを使ったソートとパフォーマンス

このNumeric#nonzero?メソッド、いいですね。すっごく便利。

nonzero? -> self | nil
自身がゼロの時 nil を返し、非ゼロの時 self を返します。
http://rurema.clear-code.com/1.9.3/method/Numeric/i/nonzero=3f.html

いま書いてるスクリプトで必要だったのは二項目とも昇順のソートだったので、sort_byで二項目配列をブロックに入れて比較項目にしました。
昇順・降順が混在するソートを書くときのためのメモ。その場合には、後者のsortを使います。