目次
sortについて
sortは配列を昇順でソートするメソッドです。要素の比較は<=>
演算子(宇宙船演算子)が広く利用されますが、<=>
演算子以外も利用できます。1
sortと<=>
を組み合わせた昇順のソート例は以下の通りです。
> (1..10).sort { |a, b| a <=> b }
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
降順の場合は以下の通りです。<=>
演算子の左辺と右辺が昇順の場合の逆になります。
> (1..10).sort { |a, b| b <=> a }
=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
sort_byについて
sort_byはブロックの評価結果を<=>
演算子を利用して昇順にソートするメソッドです。
sort_byによる昇順のソート例は以下の通りです。
> (1..10).sort_by { |a| a }
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
降順の場合は以下の通りです。降順の場合は-
をつけます。
> (1..10).sort_by { |a| -a }
=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
sortよりもsort_byを利用する
instance method Enumerable#sort_byでも紹介されている通り、sortは2要素を比較するたびに計算が必要です。一方sort_byにおける計算回数は要素数だけになります。
ですので、パフォーマンスの観点から原則sortよりもsort_byを利用するほうが好ましいです。
sort_by利用時の注意点: Integer以外の降順はうまくソートできない
以下のようにInteger以外の値は降順でうまくソートできません。
> users = [{ name: 'Tom', birth_day: '2001-10-02' }, { name: 'Bob', birth_day: '1981-03-10' }, { name: 'Alice', birth_day: '1971-08-23' }]
### Dateクラスの場合、エラーになってソートができない
> users.sort_by{ |user| -(user[:birth_day].to_date) }
# NoMethodError: undefined method `-@' for Tue, 02 Oct 2001:Date
### Stringクラスの場合、昇順でソートされてしまう(マイナスが機能していない)
> users.sort_by{ |user| -(user[:birth_day]) }
=> [{:name=>"Alice", :birth_day=>"1971-08-23"}, {:name=>"Bob", :birth_day=>"1981-03-10"}, {:name=>"Tom", :birth_day=>"2001-10-02"}]
sort_byでInteger以外を降順にソートする場合はIntegerに変換します。
うまくソートできなかった上記の例をIntegerに変換すると以下のように正しくソートできます。
> users = [{ name: 'Tom', birth_day: '2001-10-02' }, { name: 'Bob', birth_day: '1981-03-10' }, { name: 'Alice', birth_day: '1971-08-23' }]
### Integerに変換すれば降順でソートされる
> users.sort_by{ |user| -(user[:birth_day].to_i) }
=> [{:name=>"Tom", :birth_day=>"2001-10-02"}, {:name=>"Bob", :birth_day=>"1981-03-10"}, {:name=>"Alice", :birth_day=>"1971-08-23"}]
なお、sortの場合はInteger以外の降順も正しくソートできます。
> users = [{ name: 'Tom', birth_day: '2001-10-02' }, { name: 'Bob', birth_day: '1981-03-10' }, { name: 'Alice', birth_day: '1971-08-23' }]
### Dateの場合
> users.sort { |a, b| b[:birth_day].to_date <=> a[:birth_day].to_date }
=> [{:name=>"Tom", :birth_day=>"2001-10-02"}, {:name=>"Bob", :birth_day=>"1981-03-10"}, {:name=>"Alice", :birth_day=>"1971-08-23"}]
### Stringの場合
> users.sort { |a, b| b[:birth_day] <=> a[:birth_day] }
=> [{:name=>"Tom", :birth_day=>"2001-10-02"}, {:name=>"Bob", :birth_day=>"1981-03-10"}, {:name=>"Alice", :birth_day=>"1971-08-23"}]
ソートの応用例
今回はソートの応用例として以下の2つを紹介します。
- 複合条件でソートをする場合(マルチソート)
- ソート対象にnilが含まれている場合
複合条件でソートをする場合(マルチソート)
複合条件でソートする場合は配列にソート条件を優先度順で記載します。
「年齢(age)で降順、年齢が同じ場合は給料(salary)で昇順」の例は以下の通りです。
> users = [{ name: 'Tom', age: 20, salary: 300 }, { name: 'Alice', age: 20, salary: 200 }, { name: 'Bob', age: 40, salary: 800 }]
### sort_byの場合
> users.sort_by{ |user| [-user[:age], user[:salary]] }
=> [{:name=>"Bob", :age=>40, :salary=>800}, {:name=>"Alice", :age=>20, :salary=>200}, {:name=>"Tom", :age=>20, :salary=>300}]
### sortの場合
> users.sort { |a, b| [-a[:age], a[:salary]] <=> [-b[:age], b[:salary]] }
=> [{:name=>"Bob", :age=>40, :salary=>800}, {:name=>"Alice", :age=>20, :salary=>200}, {:name=>"Tom", :age=>20, :salary=>300}]
ソート対象にnilが含まれている場合
ソート対象にnilが含まれている場合正常にソートができません。具体例は以下の通りです。
> users = [{ name: 'Tom', age: 20 }, { name: 'Alice', age: nil }, { name: 'Bob', age: 40 }]
### sort_byの場合
> users.sort_by{ |user| user[:age] }
# ArgumentError: comparison of NilClass with 40 failed
### sortの場合
> users.sort { |a, b| a[:age] <=> b[:age] }
# ArgumentError: comparison of Hash with Hash failed
ソート対象にnilが含まれている場合の対処方法について、sort_byとsortそれぞれの紹介をします。
sort_byを利用する場合
OR演算子(||
)を利用してnilに対して擬似的な値を設定することでソートが可能になります。
「年齢(age)で降順。ただし、nilは最も小さい値(降順の最後尾)とする」の例は以下の通りです。
> users = [{ name: 'Tom', age: 20 }, { name: 'Alice', age: nil }, { name: 'Bob', age: 40 }]
### nilのageは-10(歳)になるので、nilが最も小さい値として扱われる
> users.sort_by{ |user| -(user[:age] || -10) }
=> [{:name=>"Bob", :age=>40}, {:name=>"Tom", :age=>20}, {:name=>"Alice", :age=>nil}]
sortを利用する場合
<=>
演算子の評価値はレシーバー側(左辺)が大きければ1、レシーバー側が小さければ-1です。2
nilを比較した際の<=>
演算子の評価値を擬似的に用意することでnilを含んだソートが可能になります。
「年齢(age)で降順。ただし、nilは最も小さい値(降順の最後尾)とする」の例は以下の通りです。
> users = [{ name: 'Tom', age: 20 }, { name: 'Alice', age: nil }, { name: 'Bob', age: 40 }]
users.sort do |a, b|
if !a[:age]
# aがnilの場合は <=> の評価を1にする
# sortは <=> の評価が1の時に[a, b]を[b, a]の順にする。
# つまり、nilは比較対象の値の後ろにソートされる
1
elsif !b[:age]
# bがnilの場合は <=> の評価を-1にする
# sortは <=> の評価が-1の時に[a, b]を[a, b]の順にする。(変わらない)
# つまり、nilは比較対象の値の後ろにソートされる
-1
else
# 降順でソート
b[:age] <=> a[:age]
end
end
=> [{:name=>"Bob", :age=>40}, {:name=>"Tom", :age=>20}, {:name=>"Alice", :age=>nil}]
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!