ガラシのパルプンテ頼み

地方産限界エンジニアのグローバル独り言

【5分くらいでわかる】sortメソッドでマルチソートする方法 | Ruby入門

今回やりたいこと

  • sortのブロック内で複数キーを用いたマルチソートを実行したい
  • 項目にnilを含む場合は、nilの項目を末尾に追いやりたい
  • その上で特定のキーで昇順、降順を制御したい

sortの挙動

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

docs.ruby-lang.org

Arrayの<=>メソッド

自身と other の各要素をそれぞれ順に <=> で比較していき、結果が 0 でなかった場合にその値を返却します。各要素が等しく、配列の長さも等しい場合には 0 を返却します。 各要素が等しいまま一方だけ配列の末尾に達した時、自身の方が短ければ -1 を、そうでなければ 1 を返却します。 other に配列以外のオブジェクトを指定した場合は nil を返却します。

docs.ruby-lang.org

  • sortのブロック内で配列同士の比較もできる。
  • 配列の先頭から比較して同値の場合には次の要素を検証していく
  • 同値じゃなければ1または-1を返す

idの降順でソート

  • 降順でのソートの場合はsortにブロックを渡して、二つ目のブロック変数を先にしてあげることで再現が可能です
customer.action_histories.sort |a, b| do 
    b.id <=> a.id
end

nameの昇順、idの降順でソート

  • 第一ソートキーであるnameで比較、そこで同値じゃなければ第二ソートキーは評価せず、右辺の大小に応じて並べ替えます
customer.action_histories.sort |a, b| do 
    [a.name, b.id] <=> [b.name, a.id]
end

日付(Date)の降順でソート

  • Dateクラスにも<=>メソッドが実装されているため考え方は配列と同様です
customer.action_histories.sort |a, b| do 
    b.action_datetime <=> a.action_datetime
end

ソート対象にnilが含まれていて、nilの項目は末尾にソート

  • nilが含まれている属性に対してnil?を実行してtrue/falseに応じて0か1を返します
  • nilの場合は0が返されて、それ以外では1が返される。降順でのソートのため0が返されたnilの項目は末尾に送られます
  • その後idがnilじゃないもの同士は1で同値となるので、第二ソートキーの昇順で並べ替えが行われます
customer.action_histories.sort |a, b| do 
    [b.id.nil? ? 0 : 1, a.action_datetime] <=> [a.id.nil? ? 0 : 1, b.action_datetime]
end