紹介: PHP 5.3+ 向けの非常に一般化されたソリューション
他の回答にはない機能を提供するため、ここに独自のソリューションを追加したいと思います。
具体的には、このソリューションの利点は次のとおりです。
- 再利用可能です。並べ替え列をハードコーディングするのではなく、変数として指定します。
- 柔軟性があります。複数の並べ替え列を (必要な数だけ) 指定できます。追加の列は、最初は等しい項目間のタイブレーカーとして使用されます。
- 可逆的です: 並べ替えを逆にするように指定できます (列ごとに個別に)。
- 拡張可能です: データセットに「愚かな」方法 (日付文字列など) で比較できない列が含まれている場合、これらの項目を直接比較できる値 (
DateTime
インスタンスなど) に変換する方法を指定することもできます。
- 必要に応じて連想します。このコードはアイテムの並べ替えを処理しますが、実際の並べ替え機能 (または)を選択します。
usort
uasort
- 最後に、それは使用しません
array_multisort
:array_multisort
は便利ですが、ソート前にすべての入力データの射影を作成することに依存します。これは時間とメモリを消費し、データセットが大きい場合は単に法外な場合があります。
コード
function make_comparer() {
// Normalize criteria up front so that the comparer finds everything tidy
$criteria = func_get_args();
foreach ($criteria as $index => $criterion) {
$criteria[$index] = is_array($criterion)
? array_pad($criterion, 3, null)
: array($criterion, SORT_ASC, null);
}
return function($first, $second) use (&$criteria) {
foreach ($criteria as $criterion) {
// How will we compare this round?
list($column, $sortOrder, $projection) = $criterion;
$sortOrder = $sortOrder === SORT_DESC ? -1 : 1;
// If a projection was defined project the values now
if ($projection) {
$lhs = call_user_func($projection, $first[$column]);
$rhs = call_user_func($projection, $second[$column]);
}
else {
$lhs = $first[$column];
$rhs = $second[$column];
}
// Do the actual comparison; do not return if equal
if ($lhs < $rhs) {
return -1 * $sortOrder;
}
else if ($lhs > $rhs) {
return 1 * $sortOrder;
}
}
return 0; // tiebreakers exhausted, so $first == $second
};
}
使い方
このセクション全体で、このサンプル データ セットを並べ替えるリンクを提供します。
$data = array(
array('zz', 'name' => 'Jack', 'number' => 22, 'birthday' => '12/03/1980'),
array('xx', 'name' => 'Adam', 'number' => 16, 'birthday' => '01/12/1979'),
array('aa', 'name' => 'Paul', 'number' => 16, 'birthday' => '03/11/1987'),
array('cc', 'name' => 'Helen', 'number' => 44, 'birthday' => '24/06/1967'),
);
基礎
この関数make_comparer
は、目的の並べ替えを定義する可変数の引数を受け入れ、usort
またはへの引数として使用することになっている関数を返しますuasort
。
最も単純な使用例は、データ項目の比較に使用したいキーを渡すことです。たとえば$data
、name
アイテムで並べ替えるには
usort($data, make_comparer('name'));
実際に見てください。
項目が数値インデックス配列の場合、キーは数値にすることもできます。質問の例では、これは
usort($data, make_comparer(0)); // 0 = first numerically indexed column
実際に見てください。
複数の並べ替え列
追加のパラメータを に渡すことで、複数の並べ替え列を指定できますmake_comparer
。たとえば、「数値」で並べ替えてから、インデックスがゼロの列で並べ替えるには、次のようにします。
usort($data, make_comparer('number', 0));
実際に見てください。
高度な機能
並べ替え列を単純な文字列ではなく配列として指定すると、より高度な機能を利用できます。この配列は、数値でインデックス付けする必要があり、次の項目が含まれている必要があります。
0 => the column name to sort on (mandatory)
1 => either SORT_ASC or SORT_DESC (optional)
2 => a projection function (optional)
これらの機能をどのように使用できるか見てみましょう。
逆ソート
名前の降順で並べ替えるには:
usort($data, make_comparer(['name', SORT_DESC]));
実際に見てください。
番号の降順で並べ替え、次に名前の降順で並べ替えるには:
usort($data, make_comparer(['number', SORT_DESC], ['name', SORT_DESC]));
実際に見てください。
カスタム プロジェクション
一部のシナリオでは、値が並べ替えに適していない列で並べ替える必要がある場合があります。サンプル データ セットの「誕生日」列はこの説明に適合します。誕生日を文字列として比較するのは意味がありません (たとえば、「1980 年 1 月 1 日」は「1970 年 10 月 10 日」よりも前になるため)。この場合、目的のセマンティクスと直接比較できる形式に実際のデータを射影する方法を指定します。
プロジェクションは、文字列、配列、または無名関数など、任意のタイプのcallableとして指定できます。射影は、1 つの引数を受け取り、その射影された形式を返すと想定されています。
プロジェクションは と ファミリで使用されるカスタム比較関数に似ていusort
ますが、より単純であり (ある値を別の値に変換するだけでよい)、 に既に組み込まれているすべての機能を利用することに注意してくださいmake_comparer
。
プロジェクションを使用せずにサンプル データ セットを並べ替えて、何が起こるか見てみましょう。
usort($data, make_comparer('birthday'));
実際に見てください。
それは望んだ結果ではありませんでした。date_create
しかし、射影として使用できます。
usort($data, make_comparer(['birthday', SORT_ASC, 'date_create']));
実際に見てください。
これは私たちが望んでいた正しい順序です。
プロジェクションで実現できることは他にもたくさんあります。たとえば、大文字と小文字を区別しない並べ替えを取得する簡単な方法はstrtolower
、射影として使用することです。
とはいえ、データセットが大きい場合は射影を使用しない方がよいことにも言及する必要があります。その場合、すべてのデータを事前に手動で射影し、射影を使用せずにソートする方がはるかに高速です。ソート速度を向上させるためにメモリ使用量を増やしました。
最後に、すべての機能を使用する例を次に示します。最初に番号の降順で並べ替え、次に誕生日の昇順で並べ替えます。
usort($data, make_comparer(
['number', SORT_DESC],
['birthday', SORT_ASC, 'date_create']
));
実際に見てください。