配列に格納されたハッシュを複数条件でソートしたい

# やりたいこと

まずは、`sort`メソッドを使い、以下の配列をheightの昇順（背の低い順）でソートします。

```ruby
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メソッド](https://docs.ruby-lang.org/ja/latest/method/Array/i/sort.html)を使用します。

sortに2つの要素を含んだブロックを渡すと、宇宙船演算子`<=>`を用いて比較をしてくれます。 

`<=>`は左側が大きいなら正の整数、同じなら0、左側が小さいなら負の整数を返す演算子です。

```ruby
puts 1 <=> 2 # -1
puts 1 <=> 1 # 0
puts 2 <=> 1 # 1
```

`<=>`の返り値を利用してsortメソッドがソートしてくれるようです。  
昇順に並べたい場合は、ブロックの第一引数（以下の例では`a`）が小さくなるようにします。

```ruby
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の降順でソートしたいと思います。

```ruby
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を返します。](https://docs.ruby-lang.org/ja/latest/method/Array/i/=3c=3d=3e.html)

```ruby
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の降順で並べるには、以下のように書きます。  

```ruby
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]`の方を左側の`[]`内に入れます。こちらの書き方でも同じ結果になります。

```ruby
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](https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/sort_by.html)を使うともっと簡単に書くことができます。**処理速度も`sort_by`の方が速い**ようです。

`sort_by`はブロックの評価結果を`<=>`で比較して、昇順でソートしてくれます。

### heightの昇順でソート

  ```ruby
  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の降順でソート

降順にしたいときはマイナスをつけます。

  ```ruby
  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`の内部での処理については[公式リファレンス](https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/sort_by.html)で紹介されていますが、理解できてはいないです。。

## 参考

- [Rubyのsortとsort_byを理解してマルチソートをしよう](https://qiita.com/zom/items/b22510a86ef63b18af00)