2017.4.26

複数のキーで配列をソートする(sort、asc、desc)

Rubyで配列に格納したデータを複数のキーでソート(昇順と降順が混在)する方法です。メモしておかないと確実に忘れると思ったので残しておきます。

コード

配列のデータ。


arr = [
    {id: 1, name: 'n01', score: 50, room: 'a'},
    {id: 2, name: 'n02', score: 80, room: 'b'},
    {id: 3, name: 'n03', score: 90, room: 'c'},
    {id: 4, name: 'n04', score: 90, room: 'b'}]

上記のデータを「sort!」メソッドを使って、「score」の降順、「room」の昇順でソート。


arr.sort! do |a, b|
    (b[:score] <=> a[:score]).nonzero? || (a[:room] <=> b[:room])
end

puts arr
# {:id=>4, :name=>"n04", :score=>90, :room=>"b"}
# {:id=>3, :name=>"n03", :score=>90, :room=>"c"}
# {:id=>2, :name=>"n02", :score=>80, :room=>"b"}
# {:id=>1, :name=>"n01", :score=>50, :room=>"a"}

「sort!」メソッドを使うと自身の配列データが並び替えられます。結果を戻り値として受け取りたい場合は「sort」を使います。


arr2 = arr.sort do |a, b|
    (b[:score] <=> a[:score]).nonzero? || (a[:room] <=> b[:room])
end

puts arr2
# {:id=>4, :name=>"n04", :score=>90, :room=>"b"}
# {:id=>3, :name=>"n03", :score=>90, :room=>"c"}
# {:id=>2, :name=>"n02", :score=>80, :room=>"b"}
# {:id=>1, :name=>"n01", :score=>50, :room=>"a"}

「<=>」は2つの配列の順序を調べるための演算子。「左辺 < 右辺」であれば -1、「左辺 > 右辺」であれば +1、同じであれば 0 を返します。

この処理が必要になる度にどっちがどっちだったか忘れそうだなと思ったら、それに関しての記事(Ruby の <=> 演算子が返す値を覚える方法 - Qiita)がやっぱりありました。


10 <=> 20   #  -1
20 <=> 10   #   1
20 <=> 20   #   0
20 <=> '20' # nil

「nonzero?」は対象のデータが0だったらnilを返します。


10.nonzero?    #=> 10
0.nonzero?     #=> nil

以下は、「sort_by」メソッドで並び替えを行う場合のコードです。


i = 0
arr3 = arr.sort_by do |a|
    [-a[:score], a[:room], i += 1]
end

puts arr3
# {:id=>4, :name=>"n04", :score=>90, :room=>"b"}
# {:id=>3, :name=>"n03", :score=>90, :room=>"c"}
# {:id=>2, :name=>"n02", :score=>80, :room=>"b"}
# {:id=>1, :name=>"n01", :score=>50, :room=>"a"}

「sort_by」を使う場合、「a[:score]」をマイナス値に変換することで降順が可能ですが、ソート対象のデータがマイナスに変換できない文字列とかだと例外エラーが発生します。(上記の場合、a[:room]を-a[:room]にするとエラー)

3番目のソート対象に「i」を指定しているのは、指定しないとソートが安定しない(a[:score]とa[:room]が同じデータがあった場合、順番が毎回変わる)ためです。

参考リンク

Ruby】関連記事