やりたいこと
まずは、sort
メソッドを使い、以下の配列をheightの昇順(背の低い順)でソートします。
people = [{ name: 'Sato', weight: 20, height: 180 },
{ name: 'Suzuki', weight: 30, height: 170 },
{ name: 'Ito', weight: 60, height: 160 },
{ name: 'Watanabe', weight: 50, height: 150 }]
その後で、複数条件でのソート(マルチソート)を試します。
例として、heightが同じ数値の場合はweightの降順(体重が上の順)でソートするようにします。
sort_by
を使った書き方も最後に紹介します。
sortメソッドを使用
heightの昇順でソート
配列の並び替えにはsortメソッドを使用します。
sortに2つの要素を含んだブロックを渡すと、宇宙船演算子<=>
を用いて比較をしてくれます。
<=>
は左側が大きいなら正の整数、同じなら0、左側が小さいなら負の整数を返す演算子です。
puts 1 <=> 2 # -1
puts 1 <=> 1 # 0
puts 2 <=> 1 # 1
<=>
の返り値を利用してsortメソッドがソートしてくれるようです。
昇順に並べたい場合は、ブロックの第一引数(以下の例ではa
)が小さくなるようにします。
puts(people.sort { |a, b| a[:height] <=> b[:height] })
# 結果
{:name=>"Watanabe", :weight=>50, :height=>150}
{:name=>"Ito", :weight=>60, :height=>160}
{:name=>"Suzuki", :weight=>30, :height=>170}
{:name=>"Sato", :weight=>20, :height=>180}
heightの昇順とweightの降順でソート
heightが同じ数値の場合はweightの降順でソートしたいと思います。
people2 = [{ name: 'Sato', weight: 20, height: 180 },
{ name: 'Suzuki', weight: 30, height: 170 },
{ name: 'Takahashi', weight: 40, height: 160 },
{ name: 'Tanaka', weight: 20, height: 160 },
{ name: 'Ito', weight: 60, height: 160 },
{ name: 'Watanabe', weight: 50, height: 150 }]
<=>
は同じ長さの配列を比較する場合、各要素の値が異なった時点でその大小に応じて1, 0, -1を返します。
puts [1, 2, 3] <=> [1, 3, 2] # -1 2つ目の要素が左の方が小さいから
puts [1, 2, 3] <=> [1, 2, 3] # 0
puts [1, 3, 2] <=> [1, 2, 3] # 1 2つ目の要素が左の方が大きいから
この返り値を利用してheightの昇順、weightの降順で並べるには、以下のように書きます。
puts(people2.sort { |a, b| [a[:height], -a[:weight]] <=> [b[:height], -b[:weight]] })
# 結果
{:name=>"Watanabe", :weight=>50, :height=>150}
{:name=>"Ito", :weight=>60, :height=>160}
{:name=>"Takahashi", :weight=>40, :height=>160}
{:name=>"Tanaka", :weight=>20, :height=>160}
{:name=>"Suzuki", :weight=>30, :height=>170}
{:name=>"Sato", :weight=>20, :height=>180}
weightは降順にしたいので、ブロックの第一引数(以下の例ではa)が大きくなるように、-a[:weight]
のようにマイナスをつけています。これで、a[:weight]
が-1
の時に+1
になります。
マイナスをつける書き方がわかりづらければ、b[:weight]
の方を左側の[]
内に入れます。こちらの書き方でも同じ結果になります。
puts(people2.sort { |a, b| [a[:height], b[:weight]] <=> [b[:height], a[:weight]] })
# 結果
{:name=>"Watanabe", :weight=>50, :height=>150}
{:name=>"Ito", :weight=>60, :height=>160}
{:name=>"Takahashi", :weight=>40, :height=>160}
{:name=>"Tanaka", :weight=>20, :height=>160}
{:name=>"Suzuki", :weight=>30, :height=>170}
{:name=>"Sato", :weight=>20, :height=>180}
sort_byメソッドを使用
Enumerable#sort_byを使うともっと簡単に書くことができます。処理速度もsort_by
の方が速いようです。
sort_by
はブロックの評価結果を<=>
で比較して、昇順でソートしてくれます。
heightの昇順でソート
puts(people.sort_by { |p| p[:height] })
# 結果
{:name=>"Watanabe", :weight=>50, :height=>150}
{:name=>"Ito", :weight=>60, :height=>160}
{:name=>"Suzuki", :weight=>30, :height=>170}
{:name=>"Sato", :weight=>20, :height=>180}
heightの昇順とweightの降順でソート
降順にしたいときはマイナスをつけます。
puts(people2.sort_by { |p| [p[:height], -p[:weight]] })
# 結果
{:name=>"Watanabe", :weight=>50, :height=>150}
{:name=>"Ito", :weight=>60, :height=>160}
{:name=>"Takahashi", :weight=>40, :height=>160}
{:name=>"Tanaka", :weight=>20, :height=>160}
{:name=>"Suzuki", :weight=>30, :height=>170}
{:name=>"Sato", :weight=>20, :height=>180}
おわりに
降順にするときになぜマイナスをつけるのか理解できずにいましたが、今回宇宙船演算子<=>
について調べて色々試すことで理解できました。
sort_by
の内部での処理については公式リファレンスで紹介されていますが、理解できてはいないです。。