より効率的なソリューション
$dict = array_flip($order);
$positions = array_map(function ($elem) use ($dict) { return $dict[$elem['id']] ?? INF; }, $array);
array_multisort($positions, $array);
すべての比較でポジションを再計算しない
配列が大きい場合、または ID の取得にコストがかかる場合、usort()
比較ごとに ID を再計算するため、使用がうまくいかないことがあります。array_multisort()
事前に計算された位置 (以下の例のmediumsort
またはを参照) を試してみてくださいfastsort
。これはそれほど複雑ではありません。
また、各比較で順序配列の id を検索しても (受け入れられた回答のように)、比較ごとに反復処理されるため、パフォーマンスは向上しません。一度計算してみてください。
以下のコード スニペットでは、主な 3 つの並べ替え関数を確認できます。
slowsort
受け入れられた答え。比較ごとに位置を検索します。
mediumsort
slowsort
事前に位置を計算することで
改善
fastsort
mediumsort
まとめて検索することを回避することで
改善されました。
これらは、フォールバック値 を提供することにより、順序で指定されていない ID を持つ要素を処理することに注意してくださいINF
。注文配列が元の配列の ID と 1 対 1 で一致する場合は、まとめて並べ替えることを避け、要素を正しい位置に挿入するだけです。cheatsort
まさにそれを行う機能を追加しました。
より一般的には、配列を重みで並べ替えることができます (weightedsort
例を参照)。優れたパフォーマンスを実現するために、重みの計算は必ず 1 回だけにしてください。
パフォーマンス (長さ 1000 の配列の場合)
fastsort about 1 ms
mediumsort about 3 ms
slowsort about 60 ms
ヒント: 配列が大きくなると、違いはさらに悪化します。
ソート機能比較
<?php
/**
* accepted answer
*
* re-evaluate position in order on each comparison
*/
function slowsort(&$array, $order, $key = 'id')
{
usort($array, function ($a, $b) use ($order, $key) {
$pos_a = array_search($a[$key], $order);
$pos_b = array_search($b[$key], $order);
return $pos_a - $pos_b;
});
}
/**
* calculate element positions once
*/
function mediumsort(&$array, $order, $key = 'id')
{
$positions = array_map(function ($elem) use ($order, $key) {
return array_search($elem[$key], $order);
}, $array);
array_multisort($positions, $array);
}
/**
* calculate positions without searching
*/
function fastsort(&$array, $order, $key = 'id')
{
$dict = array_flip($order);
$positions = array_map(function ($elem) use ($dict, $key) {
return $dict[$elem[$key]] ?? INF;
}, $array);
array_multisort($positions, $array);
}
/**
* when each order element gets used exactly once, insert elements directly
*/
function cheatsort(&$array, $order, $key = 'id')
{
$dict = array_flip($order);
$copy = $array;
foreach ($copy as $elem) {
$pos = $dict[$elem[$key]];
$array[$pos] = $elem;
}
}
/**
* Sort elements in $array by their weight given by $weight_func
*
* You could rewrite fastsort and mediumsort by replacing $position by a weight function
*/
function weightedsort(&$array, $weight_func)
{
$weights = array_map($weight_func, $array);
array_multisort($weights, $array);
}
/**
* MEASUREMENTS
*/
/**
* Generate the sorting problem
*/
function generate($size = 1000)
{
$order = array();
$array = array();
for ($i = 0; $i < $size; $i++) {
$id = random_int(0, PHP_INT_MAX);
$order[] = $id;
$array[] = array('id' => $id);
}
shuffle($order);
return [$array, $order];
}
/**
* Time $callable in ms
*/
function time_it($callable)
{
$then = microtime(true);
$callable();
$now = microtime(true);
return 1000 * ($now - $then);
}
/**
* Time a sort function with name $sort_func
*/
function time_sort($sort_func)
{
echo "Timing $sort_func", PHP_EOL;
[$array, $order] = generate();
echo time_it(function () use ($sort_func, &$array, $order) {
$sort_func($array, $order);
}) . ' ms' . PHP_EOL;
}
time_sort('cheatsort');
time_sort('fastsort');
time_sort('mediumsort');
time_sort('slowsort');