[Laravel]コレクションのソートまとめ(複数キーでソートでハマった..)

Laravel

laravelのコレクション(collection)を使って、連想配列のソート処理を行うことがありました。
複数キーでソートしたかったのですがハマったので、コレクション(collection)でのソートについてまとめてみます。

配列をソートする

昇順でソートする

昇順でソートするにはsortメソッドを呼びます。
sortを呼んだ後に続けて、valuesを呼ぶことで、キーを0番目から振り直すことができます。

下記コードはソートした後に、toArrayで配列に戻して、ddで表示しています。

$numbers = collect([8, 3, 5, 2, 5, 1]);
$sorted = $numbers->sort()->values()->toArray();
dd($sorted);

結果は下記のようになります。

array:6 [▼
  0 => 1
  1 => 2
  2 => 3
  3 => 5
  4 => 5
  5 => 8
]

降順でソートする

呼び出せそうなメソッドが見当たりませんでした😭
php標準のarsortを使ってあげる方法しかなさそうです。

$numbers = collect([8, 3, 5, 2, 5, 1]);
$sorted = $numbers->sort()->values()->toArray();
arsort($sorted);
dd($sorted);
array:6 [▼
  5 => 8
  3 => 5
  4 => 5
  2 => 3
  1 => 2
  0 => 1
]

キーを維持してソートする(valuesを呼び出さない)

単純にsortだけを呼んだ場合はキーが並び替え前と同じになります。

$numbers = collect([8, 3, 5, 2, 5, 1]);
$sorted = $numbers->sort()->toArray();
dd($sorted);

結果は下記のようになります。

array:6 [▼
  5 => 1
  3 => 2
  1 => 3
  2 => 5
  4 => 5
  0 => 8
]

連想配列をソートする

下記の連想配列を使って試してみます。

$fruits = collect([
    [
        'name' => 'banana',
        'price' => '120'
    ],
    [
        'name' => 'apple',
        'price' => '150'
    ],
    [
        'name' => 'grape',
        'price' => '300'
    ],
    [
        'name' => 'orange',
        'price' => '98'
    ]
]);

昇順にソートする

sortByメソッドを呼び出して、キー名を書くと昇順にソートされます。

$sorted = $fruits->sortBy('name')->values()->toArray();
dd($sorted);

結果はこうなります。

array:4 [▼
  0 => array:2 [▼
    "name" => "apple"
    "price" => "150"
  ]
  1 => array:2 [▼
    "name" => "banana"
    "price" => "120"
  ]
  2 => array:2 [▼
    "name" => "grape"
    "price" => "300"
  ]
  3 => array:2 [▼
    "name" => "orange"
    "price" => "98"
  ]
]

priceを指定して、価格の安い順にしてみます。
こう書いて実行すると…

$sorted = $fruits->sortBy('price')->values()->toArray();
dd($sorted);

結果はこうなって、価格が安い順になりました。

array:4 [▼
  0 => array:2 [▼
    "name" => "orange"
    "price" => "98"
  ]
  1 => array:2 [▼
    "name" => "banana"
    "price" => "120"
  ]
  2 => array:2 [▼
    "name" => "apple"
    "price" => "150"
  ]
  3 => array:2 [▼
    "name" => "grape"
    "price" => "300"
  ]
]

降順にソートする

sortByDescメソッドを呼び出して、キー名を書くと降順にソートされます。

$sorted = $fruits->sortByDesc('name')->values()->toArray();
dd($sorted);

結果はこうなります。

array:4 [▼
  0 => array:2 [▼
    "name" => "orange"
    "price" => "98"
  ]
  1 => array:2 [▼
    "name" => "grape"
    "price" => "300"
  ]
  2 => array:2 [▼
    "name" => "banana"
    "price" => "120"
  ]
  3 => array:2 [▼
    "name" => "apple"
    "price" => "150"
  ]
]

複数のキーでソートする

今回ここでつまづきました…😵

複数のキーでソートする場合、
sortBy(‘hoge’)->sortBy(‘fuga’)とメソッドチェインすれば上手くソートできるんじゃない?と思って書いてみましたが、上手くソートされず。。

sortメソッドを使って、コールバック関数に並び順を定義する必要があるみたいでした。

具体的には下記のような連想配列の場合に…

$fruits = collect([
    [
        'name' => 'banana',
        'price' => '120',
        'group_code' => 1
    ],
    [
        'name' => 'apple',
        'price' => '150',
        'group_code' => 2
    ],
    [
        'name' => 'grape',
        'price' => '300',
        'group_code' => 1
    ],
    [
        'name' => 'orange',
        'price' => '98',
        'group_code' => 3
    ]
]);

group_codeで昇順にした後に、価格の降順に並べ替えようとします。

こう書いても上手くいかないので…(ソートが効かない…)

$fruits->sortBy('group_code')->sortByDesc('price')->values()->toArray();

下記のように書くとソートされます。

$sorted = $fruits->sort(function($first, $second) {
    if ($first['group_code'] == $second['group_code']) {
        return $first['price'] < $second['price'] ? 1 : -1;
    }

    return $first['group_code'] > $second['group_code'] ? 1 : -1;
})->toArray();
dd($sorted);

sortメソッドのコールバック関数を見てみると…

$firstと$secondは配列のそれぞれの値が入ってきて比較されます。

関数の中では 1 / -1 / 0 を返します。
最初にgroup_codeを比較します。
$firstと$secondを比較して、$secondのgroup_codeの方が小さい場合は 1 を返します。
これでgroup_codeの小さい順になります。

group_codeが同じ場合は、priceを比較します。
$firstと$secondを比較して、$secondのpriceが大きい場合に 1 を返しています。
これで、group_codeが同じ場合は、priceを比較して大きい順にソートされます。

そうすると結果が次の通り、意図したものになります。

array:4 [▼
  2 => array:3 [▼
    "name" => "grape"
    "price" => "300"
    "group_code" => 1
  ]
  0 => array:3 [▼
    "name" => "banana"
    "price" => "120"
    "group_code" => 1
  ]
  1 => array:3 [▼
    "name" => "apple"
    "price" => "150"
    "group_code" => 2
  ]
  3 => array:3 [▼
    "name" => "orange"
    "price" => "98"
    "group_code" => 3
  ]
]

sort()の中身を確認してみる

ちょっとよくわからないので..
コレクションから呼ばれているsortのソースを確認してみます…

callableが引数で指定されていない場合は、php標準のasortがつかわれています。
指定されている場合は、uasortが使われています。

/**
 * Sort through each item with a callback.
 *
 * @param  callable|null  $callback
 * @return static
 */
public function sort(callable $callback = null)
{
    $items = $this->items;

    $callback
        ? uasort($items, $callback)
        : asort($items);

    return new static($items);
}

つまり、連想配列のキーで複数ソートをする場合はphpの標準のuasortを使うことになります。
uasortの公式ドキュメントはこんな感じです。

この関数は、インデックスとそれに対応する要素を関連づけた配列をソートします。 ソートには、ユーザー定義の比較関数を使います。

ドキュメントを見ると上記のような記載があります。
ユーザー定義の比較関数っていうのが、前述したような書き方で、前と後の配列の値を比べるような処理を書かないといけないようです。

usortの説明のところに例が載っています。
なるほど…

まとめ

laravelのcollectionでソートする場合のやり方を見ていきました。
連想配列の複数キーでソートする場合は、sortにユーザー定義の比較関数を定義してソートする必要がありました。

sortメソッドをみると、php標準のuasortが使われていました。
連想配列のソートするキーが増えるほど、ユーザー定義の比較関数のネストが深くなって見づらくなっていくことが想定されますが仕方なさそうです。

もっと簡単にコレクションをソートできるよ!💪🏻 という場合は教えていただければ😞

コメント

タイトルとURLをコピーしました