提问人:Honus Wagner 提问时间:6/22/2012 最后编辑:crmpiccoHonus Wagner 更新时间:12/15/2022 访问量:92012
按自定义顺序对数组的 php 数组进行排序
Sorting a php array of arrays by custom order
问:
我有一个数组:
Array (
[0] => Array (
[id] = 7867867,
[title] = 'Some Title'),
[1] => Array (
[id] = 3452342,
[title] = 'Some Title'),
[2] => Array (
[id] = 1231233,
[title] = 'Some Title'),
[3] => Array (
[id] = 5867867,
[title] = 'Some Title')
)
需要按特定顺序进行:
- 3452342
- 5867867
- 7867867
- 1231233
我该怎么做呢?我以前对数组进行过排序,并阅读了很多其他关于它的文章,但它们总是基于比较的(即 valueA < valueB)。
感谢帮助。
答:
如果要保持索引关联,则需要定义自己的比较函数并使用 usort
或 uasort
。
评论
您可以使用它来精确地指示数组的排序方式。在这种情况下,可以在比较函数中使用数组。usort()
$order
下面的示例使用闭合
来使生活更轻松。
$order = array(3452342, 5867867, 7867867, 1231233);
$array = array(
array('id' => 7867867, 'title' => 'Some Title'),
array('id' => 3452342, 'title' => 'Some Title'),
array('id' => 1231233, 'title' => 'Some Title'),
array('id' => 5867867, 'title' => 'Some Title'),
);
usort($array, function ($a, $b) use ($order) {
$pos_a = array_search($a['id'], $order);
$pos_b = array_search($b['id'], $order);
return $pos_a - $pos_b;
});
var_dump($array);
这项工作的关键是将要比较的值作为数组中 s 的位置。id
$order
比较函数的工作原理是查找数组中要比较的两个项目的 id 的位置。如果在数组中出现,则函数的返回值将为负值(较小,因此“浮动”到顶部)。如果紧随其后,则该函数返回一个正数(更大,因此“下沉”)。$order
$a['id']
$b['id']
$order
$a
$a['id']
$b['id']
$a
最后,使用闭包没有特殊原因;这只是我快速编写这些一次性函数的首选方式。它同样可以使用普通的命名函数。
评论
扩展 salathe 对这一附加要求的回答:
现在,当我将项目添加到数组而不是排序中时会发生什么?我 不在乎它们出现的顺序,只要它排在那些之后 我确实指定了。
您需要在排序函数中添加两个附加条件:
- “不在乎”项目必须被视为大于“自定义”项目
- 两个“不在乎”项目必须被视为相等(您可以为这种情况添加决胜条件)
因此,修改后的代码将是:
$order = array(
3452342,
5867867,
7867867,
1231233
);
$array = array(
array("id" => 7867867, "title" => "Must Be #3"),
array("id" => 3452342, "title" => "Must Be #1"),
array("id" => 1231233, "title" => "Must Be #4"),
array("id" => 5867867, "title" => "Must Be #2"),
array("id" => 1111111, "title" => "Dont Care #1"),
array("id" => 2222222, "title" => "Dont Care #2"),
array("id" => 3333333, "title" => "Dont Care #3"),
array("id" => 4444444, "title" => "Dont Care #4")
);
shuffle($array); // for testing
var_dump($array); // before
usort($array, function ($a, $b) use ($order) {
$a = array_search($a["id"], $order);
$b = array_search($b["id"], $order);
if ($a === false && $b === false) { // both items are dont cares
return 0; // a == b (or add tie-breaker condition)
} elseif ($a === false) { // $a is a dont care
return 1; // $a > $b
} elseif ($b === false) { // $b is a dont care
return -1; // $a < $b
} else {
return $a - $b; // sort $a and $b ascending
}
});
var_dump($array); // after
输出:
Before | After
-------------------------------+-------------------------------
array(8) { | array(8) {
[0]=> | [0]=>
array(2) { | array(2) {
["id"]=> | ["id"]=>
int(4444444) | int(3452342)
["title"]=> | ["title"]=>
string(12) "Dont Care #4" | string(10) "Must Be #1"
} | }
[1]=> | [1]=>
array(2) { | array(2) {
["id"]=> | ["id"]=>
int(3333333) | int(5867867)
["title"]=> | ["title"]=>
string(12) "Dont Care #3" | string(10) "Must Be #2"
} | }
[2]=> | [2]=>
array(2) { | array(2) {
["id"]=> | ["id"]=>
int(1231233) | int(7867867)
["title"]=> | ["title"]=>
string(10) "Must Be #4" | string(10) "Must Be #3"
} | }
[3]=> | [3]=>
array(2) { | array(2) {
["id"]=> | ["id"]=>
int(1111111) | int(1231233)
["title"]=> | ["title"]=>
string(12) "Dont Care #1" | string(10) "Must Be #4"
} | }
[4]=> | [4]=>
array(2) { | array(2) {
["id"]=> | ["id"]=>
int(5867867) | int(2222222)
["title"]=> | ["title"]=>
string(10) "Must Be #2" | string(12) "Dont Care #2"
} | }
[5]=> | [5]=>
array(2) { | array(2) {
["id"]=> | ["id"]=>
int(2222222) | int(1111111)
["title"]=> | ["title"]=>
string(12) "Dont Care #2" | string(12) "Dont Care #1"
} | }
[6]=> | [6]=>
array(2) { | array(2) {
["id"]=> | ["id"]=>
int(3452342) | int(3333333)
["title"]=> | ["title"]=>
string(10) "Must Be #1" | string(12) "Dont Care #3"
} | }
[7]=> | [7]=>
array(2) { | array(2) {
["id"]=> | ["id"]=>
int(7867867) | int(4444444)
["title"]=> | ["title"]=>
string(10) "Must Be #3" | string(12) "Dont Care #4"
} | }
} | }
评论
array("id" => 5867867, "title" => "Must Be #2")
array("id" => 7867867, "title" => "Must Be #3")
@salathe 对于那些很难理解 salathe's usort 在做什么的人:
$array中的每个项目都是锦标赛中的“冠军”,位于新阵列的开头(除了不是第一名,而是希望成为第一名)。
$a是主场冠军,$b一场比赛中的对手冠军。
回调中的 $pos_a 和 $pos_b 是将在争夺冠军 A 和 B 时使用的属性。在本例中,此属性是 $order 中冠军 ID 的索引。
然后是回归时的战斗。现在我们看看是多还是少属性更好。在一场 usort 战斗中,主场冠军想要一个负数,这样他就可以更快地进入阵列。客场冠军想要一个正数。如果有 0,那就是平局。
因此,按照这个类比,当从主队属性中减去客场冠军属性($order中的索引)时,客场冠军属性越大,获得正数获胜的可能性就越小。但是,如果您要颠倒属性的使用方式,现在主场冠军的属性将从客场冠军的属性中减去。在这种情况下,客场冠军的数字越大,他的比赛就越有可能以正数结束。
代码如下所示:
注意:代码运行多次,就像真正的锦标赛有很多战斗来决定谁先获得(即 0 / 数组的开始)
//tournament with goal to be first in array
usort($champions, function ($home, $away) use ($order) {
$home_attribute = array_search($a['id'], $order);
$away_attribute = array_search($b['id'], $order);
//fight with desired outcome for home being negative and away desiring positive
return $home_attribute - $away_attribute;
});
评论
使用迭代调用的方法的其他答案效率不高。通过重构/翻转“顺序”查找数组,您可以完全省略所有调用 - 使您的任务更加高效和简短。我将使用最现代的“宇宙飞船操作员”(),但早期的技术对于比较线的工作方式相同。“null 合并运算符”() 将以与 or 相同的方式检查查找数组中给定值是否存在 -- 这总是比 or 更有效。array_search()
array_search()
<=>
??
id
isset()
array_search()
in_array()
代码: (Demo) (Demo with 7.4 arrow function syntax) (Demo with lower than PHP7)
// restructure with values as keys, and keys as order (ASC)
$order = array_flip([3452342, 5867867, 7867867, 1231233]);
// generating $order = [3452342 => 0, 5867867 => 1, 7867867 => 2, 1231233 => 3];
$default = count($order);
// generating $default = 4
usort($array, function($a, $b) use($order, $default) {
return ($order[$a['id']] ?? $default) <=> ($order[$b['id']] ?? $default);
});
var_export($array);
没有排序,你也可以得到它。
如果没有重复的 ID 并且包含 中的所有值,并且 中的列包含 中的所有值,则可以通过将值翻转到 中的键,然后将临时的第一级键分配给数组,然后合并或替换为 来获得相同的结果。
$order
id
$array
id
$array
$order
$order
$array
$order
$order = array(3452342, 5867867, 7867867, 1231233); $array = array( array('id' => 7867867, 'title' => 'Some Title'), array('id' => 3452342, 'title' => 'Some Title'), array('id' => 1231233, 'title' => 'Some Title'), array('id' => 5867867, 'title' => 'Some Title'), ); $order = array_flip($order); $array = array_column($array,null,"id"); $result = array_replace($order,$array); var_dump(array_values($result));
在以下位置具有可能重复的 ID:
$array
$order = array(3452342, 5867867, 7867867, 1231233); $array = array( array('id' => 7867867, 'title' => 'Some Title'), array('id' => 3452342, 'title' => 'Some Title'), array('id' => 1231233, 'title' => 'Some Title'), array('id' => 5867867, 'title' => 'Some Title'), ); $order_dict = array_flip($order); $order_dict = array_combine($order, array_fill(0, count($order), [])); foreach($array as $item){ $order_dict[$item["id"]][] = $item; } //$order_dict = array_filter($order_dict); // if there is empty item on some id in $order array $result = []; foreach($order_dict as $items){ foreach($items as $item){ $result[] = $item; } } var_dump($result);
评论
array_flip()
array_combine(array_fill())
array_fill_keys()
更高效的解决方案
$dict = array_flip($order);
$positions = array_map(function ($elem) use ($dict) { return $dict[$elem['id']] ?? INF; }, $array);
array_multisort($positions, $array);
不要在每次比较中重新计算位置
当您的数组很大或获取 id 的成本更高时,使用可能会变得很糟糕,因为您要为每次比较重新计算 id。尝试使用预先计算的位置array_multisort()(
请参阅下面的示例),这并不复杂。usort()
mediumsort
fastsort
此外,在每次比较(如接受的答案)中搜索 order 数组中的 id 不会提高性能,因为每次比较都会遍历它。计算一次。
在下面的代码片段中,您可以看到主要的三个排序函数:
slowsort
公认的答案。在每次比较中搜索位置。mediumsort
通过提前计算仓位进行改进slowsort
fastsort
通过避免搜索 alltogher 进行了改进。mediumsort
请注意,这些元素通过提供回退值来处理 id 未按顺序给定的元素。如果您的顺序数组与原始数组的 ID 1 对 1 匹配,则避免全部排序,只需将元素插入正确的位置即可。我添加了一个功能来做到这一点。INF
cheatsort
您可以更普遍地按权重对数组进行排序(请参阅示例)。确保只计算一次重量,以获得良好的性能。weightedsort
性能(对于长度为 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');
评论
slowsort()
、 和 不适用于要排序的输入数组中缺少值的顺序数组。mediumsort()
cheatsort()
我遇到了同样的问题,@mickmackusa得到了我需要的答案。当有值时,所选答案不会排序。例如:NULL
$order = array(3, 2, 10);
$array = array(
array('id' => NULL, 'title' => 'any order since null but not top'),
array('id' => NULL, 'title' => 'any order since null but not top'),
array('id' => NULL, 'title' => 'any order since null but not top'),
array('id' => 2, 'title' => 'should be top'),
);
usort($array, function ($a, $b) use ($order) {
$pos_a = array_search($a['id'], $order);
$pos_b = array_search($b['id'], $order);
return $pos_a - $pos_b;
});
上面的结果将显示以下输出:
array(4) {
[0]=>
array(2) {
["id"]=>
NULL
["title"]=>
string(32) "any order since null but not top"
}
[1]=>
array(2) {
["id"]=>
NULL
["title"]=>
string(32) "any order since null but not top"
}
[2]=>
array(2) {
["id"]=>
NULL
["title"]=>
string(32) "any order since null but not top"
}
[3]=>
array(2) {
["id"]=>
int(2)
["title"]=>
string(13) "should be top"
}
}
在@mickmackusa的回答中,它不仅消除了排序中的空,而且还在顺序基础上放入了第一个可用的内容。因此,由于在数组中唯一可用的是 2,那么这将是最重要的。
虽然它在 PHP 5.6 中不起作用。所以我把它转换为PHP 5.6兼容。这就是我得到的
usort($array, function($a, $b) use($order, $default) {
$a = (isset($order[$a['id']]) ? $order[$a['id']] : $default);
$b = (isset($order[$b['id']]) ? $order[$b['id']] : $default);
if($a == $b) return 0;
elseif($a > $b) return 1;
return -1;
});
上述排序的结果将是
array(4) {
[0]=>
array(2) {
["id"]=>
int(2)
["title"]=>
string(13) "should be top"
}
[1]=>
array(2) {
["id"]=>
NULL
["title"]=>
string(32) "any order since null but not top"
}
[2]=>
array(2) {
["id"]=>
NULL
["title"]=>
string(32) "any order since null but not top"
}
[3]=>
array(2) {
["id"]=>
NULL
["title"]=>
string(32) "any order since null but not top"
}
}
我希望我对代码的转换能帮助在具有较低 php 版本的过时服务器上工作的开发人员。
评论
elseif($a < $b) return -1;
return -1;
true
评论