按自定义顺序对数组的php数组进行排序
Posted
技术标签:
【中文标题】按自定义顺序对数组的php数组进行排序【英文标题】:Sorting a php array of arrays by custom order 【发布时间】:2012-06-24 02:45:12 【问题描述】:我有一个数组数组:
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
感谢您的帮助。
【问题讨论】:
您如何知道订单应该满足您的需求? @Telshin 他只知道,好吗? :) 例如,我对 csv 导出中的字段有默认顺序。顺序有些随意(至少不是按字母顺序)。但我仍然需要对其他数组进行排序以匹配它。 【参考方案1】:您可以使用usort()
来精确指定数组的排序方式。在这种情况下,$order
数组可以在比较函数中使用。
以下示例使用closure
让生活更轻松。
$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);
这项工作的关键是让要比较的值成为id
s 在$order
数组中的位置。
比较函数的工作原理是在 $order
数组中查找要比较的两个项目的 id 的位置。如果$a['id']
在$order
数组中出现在$b['id']
之前,则函数的返回值将为负数($a
较少“浮动”到顶部)。如果$a['id']
出现在$b['id']
之后,则该函数返回一个正数($a
更大,因此“下沉”)。
最后,使用闭包并没有什么特别的原因;这只是我快速编写这些一次性函数的首选方式。它同样可以使用普通的命名函数。
【讨论】:
太棒了。谢谢。现在,当我将项目添加到数组而不是排序时会发生什么?从逻辑上讲,我不在乎它们出现的顺序,只要它出现在我指定的顺序之后。我该如何处理? 如果我有一个简单的数组,最好的解决方案是:$array = (0=> values, 1=>values,2=>values,etc) by given array(5,2,4, 1,3,0)? 好的,经过 5 秒的研究,我发现了这个:***.com/questions/348410/…。 如果您想按没有唯一值的键进行排序怎么办?例如,如果您按标题排序,但可能有多个标题。在这种情况下可能没问题,但可能还有其他键,例如日期或作者。 如果其中之一可以为空怎么办?像这个 id 7867867 不存在但你仍然要考虑订单【参考方案2】:扩展 salathe's answer 以满足此附加要求:
现在,当我将项目添加到数组而不是排序时会发生什么?一世 不在乎它们出现的顺序,只要它们出现在它们之后 我确实指定了。
需要在排序函数中添加两个附加条件:
-
必须将“无关”项目视为大于列入白名单的项目
必须将两个“无关”项视为相等
所以修改后的代码是:
$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
else if ($a === false) // $a is a dont care
return 1; // $a > $b
else if ($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")
为 #3 所以我需要 #2 为空白怎么办
else if
作为两个词违反了 PSR-12 编码标准。在php中应该是一个字:elseif
.【参考方案3】:
使用带有array_search()
迭代调用的方法的其他答案并不像它们那样有效。通过重组/翻转“订单”查找数组,您可以完全省略所有array_search()
调用——使您的任务更加高效和简洁。我将使用最现代的“宇宙飞船操作员”(<=>
),但早期的技术对于比较线也同样适用。 “空合并运算符” (??
) 将检查查找数组中是否存在给定的id
值,其方式与isset()
相同——这总是比array_search()
或@ 更有效987654330@.
代码:(Demo) (Demo with 7.4 arrow function syntax)
// 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);
【讨论】:
【参考方案4】:如果要保持索引关联,则需要定义自己的比较函数并使用usort
或uasort
。
【讨论】:
这个部分解决方案在问题发布后仅 5 分钟就出现了。它的价值很低,因为它本质上只提供了两个指向手册的超链接,最好作为评论放置。 5 年后,这个答案被完整的答案所掩盖,使这篇文章毫无用处。请考虑删除。 (不会对这篇文章投反对票)【参考方案5】:更高效的解决方案
$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
),这并不复杂。
在每次比较时在 order 数组中搜索 id(就像在接受的答案中一样)也不会提高性能,因为每次比较都会对其进行迭代。计算一次。
在下面的代码sn-p中可以看到主要的三个排序函数:
slowsort
接受的答案。搜索每次比较的位置。
mediumsort
通过提前计算位置改进slowsort
fastsort
通过避免全部搜索改进了mediumsort
。
请注意,通过提供一个后备值INF
,这些处理具有未按顺序给出的 id 的元素。如果您的订单数组与原始数组的 id 1 对 1 匹配,那么避免一起排序,只在正确的位置插入元素是要走的路。我添加了一个函数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');
【讨论】:
【参考方案6】:不用排序也可以得到。
如果没有重复的 id 并且$order
包含来自$array
的所有id
值,并且$array
中的id
列包含$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));
$array
中可能存在重复的 ID:
$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);
【讨论】:
2. 中的array_flip()
有什么意义。如果您要立即覆盖下一行的变量。为什么array_combine(array_fill())
?你喜欢array_fill_keys()
吗?第二个 sn-p 正在做 6 种迭代技术——你会在实际项目中这样做吗?【参考方案7】:
@salathe 对于那些难以理解 salathe 的 usort 在做什么的人:
$array 中的每个项目都是锦标赛中的“冠军”,位于新数组的开头(除了他们希望成为第 0 而不是第 0)。
$a 是主场冠军,$b 是对手冠军。
回调中的$pos_a 和$pos_b 是用于争夺冠军a 和b 的属性。在这种情况下,此属性是 $order 中冠军 id 的索引。
然后是返回时的战斗。现在我们看看是否拥有更多或更少的属性更好。在一次排序战中,主队冠军想要一个负数,这样他就可以更快地进入阵列。客场冠军想要一个正数。如果有 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;
);
【讨论】:
希望这不会进一步混淆人们;) 我认为这只会增加混乱:“与期望的结果作斗争,家庭是消极的,而远离是积极的”我认为这种说法根本不准确。以上是关于按自定义顺序对数组的php数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章
如何按自定义顺序对 JavaScript 中的数组进行排序? [复制]