按自定义顺序对数组的 php 数组进行排序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11145393/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 23:50:51  来源:igfitidea点击:

Sorting a php array of arrays by custom order

phparraysmultidimensional-arraysorting

提问by Honus Wagner

I have an array of arrays:

我有一个数组数组:

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')
)

The need to go in a specific order:

需要按特定顺序进行:

  1. 3452342
  2. 5867867
  3. 7867867
  4. 1231233
  1. 3452342
  2. 5867867
  3. 7867867
  4. 1231233

How would I go about doing that? I have sorted arrays before, and read plenty of other posts about it, but they are always comparison based (i.e. valueA < valueB).

我该怎么做?我以前对数组进行过排序,并阅读了很多关于它的其他文章,但它们总是基于比较(即 valueA < valueB)。

Help is appreciated.

帮助表示赞赏。

回答by salathe

You can use usort()to dictate precisely how the array is to be sorted. In this case, the $orderarray can be used within the comparison function.

您可以使用usort()来精确指定数组的排序方式。在这种情况下,$order可以在比较函数中使用数组。

The example below uses a closureto make life easier.

下面的示例使用 aclosure使生活更轻松。

$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);

The key to this working is having the values that are being compared, be the positions of the ids within the $orderarray.

这项工作的关键是拥有正在比较的值,即数组中ids的位置$order

The comparison function works by finding the positions of the ids of two items to be compared within the $orderarray. If $a['id']comes before $b['id']in the $orderarray, then the return value of the function will be negative ($ais less so "floats" to the top). If $a['id']comes after $b['id']then the function returns a positive number ($ais greater so "sinks" down).

比较函数的工作原理是查找要在$order数组中进行比较的两个项目的 id 的位置。如果$a['id']出现$b['id']$order数组之前,则函数的返回值将为负数($a更小,因此“浮动”到顶部)。如果$a['id']在之后,$b['id']则该函数返回一个正数($a更大所以“下沉”)。

Finally, there is no special reason for using a closure; it's just my go-to way of writing these sorts of throwaway functions quickly. It could equally use a normal, named function.

最后,使用闭包没有特殊原因;这只是我快速编写此类一次性函数的首选方式。它同样可以使用普通的命名函数。

回答by Salman A

Extending salathe's answerfor this additional requirement:

扩展Salathe对这个额外要求的回答

Now what happens when I add items to the array and not to the sort? I don't care what order they appear, as long as it comes after the ones that I did specify.

现在当我将项目添加到数组而不是排序时会发生什么?我不在乎它们出现的顺序,只要它出现在我指定的顺序之后。

You need to add two additional conditions in the sorting function:

您需要在排序功能中添加两个附加条件:

  1. A "dont care" item must be considered greater than whitelisted items
  2. Two "dont care" items must be considered equal
  1. 必须将“不在乎”的项目视为大于列入白名单的项目
  2. 两个“不在乎”的项目必须被视为相等

So the revised code would be:

所以修改后的代码是:

$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

Output:

输出:

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"
  }                            |    }
}                              |  }

回答by mickmackusa

The other answers which are using methods with iterated calls of array_search()are not as efficient as they can be. By restructuring/flipping the "order" lookup array, you can completely omit all array_search()calls -- making your task much more efficient and brief. I'll use the most modern "spaceship operator" (<=>), but earlier techniques will work the same for the comparison line.

使用迭代调用方法的其他答案array_search()并不像它们一样有效。通过重组/翻转“订单”查找数组,您可以完全省略所有array_search()调用——使您的任务更加高效和简短。我将使用最现代的“飞船操作员”( <=>),但早期的技术对比较线的作用相同。

Method #1- usortwhen all idvalues exist in $order(Demo)

方法 #1-usort当所有id值都存在于$order( Demo)

$order=array_flip([3452342,5867867,7867867,1231233]);  // restructure with values as keys, and keys as order (ASC)
// generating $order=[3452342=>0,5867867=>1,7867867=>2,1231233=>3];
$array=[
    ['id'=>7867867,'title'=>'Some Title'],
    ['id'=>3452342,'title'=>'Some Title'],
    ['id'=>1231233,'title'=>'Some Title'],
    ['id'=>5867867,'title'=>'Some Title']
];

usort($array,function($a,$b)use($order){
    return $order[$a['id']]<=>$order[$b['id']];
    // when comparing ids 3452342 & 1231233, the actual comparison is 0 vs 3
});
// uasort() if you want to preserve keys

var_export($array);


Method #2- usortwhen some idvalues do not exist in $order(Demo)
*note, isset()is a less expensive call than array_search()

方法 #2-usort当某些id值在$order( Demo)
*note 中不存在时,isset()是一个比调用更便宜的调用array_search()

$order=array_flip([3452342,5867867,7867867,1231233]);  // restructure with values as keys, and keys as order (ASC)
// generating $order=[3452342=>0,5867867=>1,7867867=>2,1231233=>3];
$outlier=1+max($order);
// generating $outlier=4
$array=[
    ['id'=>7867867,'title'=>'Some Title'],
    ['id'=>3452342,'title'=>'Some Title'],
    ['id'=>'foo','title'=>'Some Title'],
    ['id'=>1231233,'title'=>'Some Title'],
    ['id'=>'bar','title'=>'Some Title'],
    ['id'=>5867867,'title'=>'Some Title']
];

usort($array,function($a,$b)use(&$order,$outlier){  // make $order modifiable with &
    if(!isset($order[$a['id']])){$order[$a['id']]=$outlier;}  // update lookup array with [id]=>[outlier number]
    if(!isset($order[$b['id']])){$order[$b['id']]=$outlier;}  // and again
    return $order[$a['id']]<=>$order[$b['id']];
});

var_export($array);


Alternate Method #2- usortwhen some idvalues do not exist in $order

替代方法 #2-usort当某些id值不存在于$order

...I'd also like to mention that in some cases, avoiding the iterated double-call of isset()may be less attractive versus fully preparing the $orderarray prior to calling usort().

...我还想提一下,在某些情况下,isset()$order在调用usort().

This one-liner will ensure that there are no missing idvalues, thus eliminating the need for anything other than the comparison line inside the sorting function. (Full Snippet Demo)

这种单行将确保没有缺失id值,从而消除排序函数中除比较行以外的任何其他内容。(完整片段演示

$order=array_replace(array_fill_keys(array_column($array,'id'),$outlier),$order);

回答by Nev Stokes

You need to define your own comparison function and use usortor uasortif you want to maintain index association.

您需要定义自己的比较函数并使用usortuasort如果要维护索引关联。

回答by Kris Roofe

Without sort you also can get it.

没有排序你也可以得到它。

It there is no duplicate id;

没有重复的id;

<?php

    $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));

With duplicate id,

使用重复的 ID,

<?php

    $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);

回答by Jason Basanese

@salathe For those of you that are having a hard time understanding what salathe's usort is doing:

@salathe 对于那些很难理解 salathe 的 usort 正在做什么的人:

Each item in $array is a 'champion' in a tournament to be at the start of a new array (except instead of being number one they want to be number 0).

$array 中的每个项目都是锦标赛中的“冠军”,位于新数组的开头(除非他们希望成为数字 0,而不是第一名)。

$a is the home champion, and $b the opponent champion in a match.

$a 是主场冠军,$b 是一场比赛中的对手冠军。

$pos_a and $pos_b from the callback are what attributes will be used in the fight for champion a and b. In this case this attribute is the index of the champions id in $order.

回调中的 $pos_a 和 $pos_b 是在争夺冠军 a 和 b 时将使用的属性。在这种情况下,此属性是 $order 中冠军 id 的索引。

Then there is the fight at the return. Now we look to see if having more or less of the attribute is better. In a usort battle the home champion wants a negative number so he can be sooner in the array. The away champion wants a positive number. And should there be a 0 it is a tie.

然后是返回时的战斗。现在我们看看属性越多越好还是越少越好。在 usort 战斗中,主场冠军想要一个负数,这样他就可以更快地进入阵列。客场冠军想要一个正数。如果有一个 0 它是平局。

So following this analogy when away champions attribute(index in $order) is subtracted from the home teams attribute, the larger the away champions attribute is the less likely it is to win by getting a positive number. However if you were to reverse the way the attributes are used, now home champion's attribute is subtracted from away champion's. In this case a larger number for the away champion is more likely to make him have the match end in a positive number.

因此,当从主队属性中减去客场冠军属性($order 中的索引)时,按照这个类比,客场冠军属性越大,通过获得正数获胜的可能性就越小。但是,如果您颠倒使用属性的方式,现在从客场冠军的属性中减去主场冠军的属性。在这种情况下,客队冠军的较大数字更有可能使他以正数结束比赛。

Code would look like:

代码看起来像:

note: code is run many times much like a real tournament has many battles in order to decide who gets first(ie 0 / start of array)

注意:代码运行很多次,就像一场真正的锦标赛有很多场战斗,以确定谁先获得(即 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;
    });

回答by Josef Wittmann

More Efficient Solution

更高效的解决方案

$dict = array_flip($order);
$positions = array_map(function ($elem) use ($dict) { return $dict[$elem['id']] ?? INF; }, $array);
array_multisort($positions, $array);

Don't recalculate positions on every comparison

不要在每次比较时重新计算位置

When your array is large or getting the id is costlier, using usort()can get bad, because you recalculate the id for each comparison. Try array_multisort()with pre-calculated positions (see mediumsortor fastsortin the example below), which isn't any more complicated.

当您的数组很大或获取 id 的成本更高时,使用usort()可能会变得糟糕,因为您会为每次比较重新计算 id。尝试array_multisort()使用预先计算好的位置(参见mediumsortfastsort在下面的示例中),这并不复杂。

Also searching for the id in the order array on each comparison (like in the accepted answer) doesn't improve performance, since you iterate over it every comparison. Calculate it once.

同样在每次比较时在 order 数组中搜索 id (如在接受的答案中)并不会提高性能,因为您每次比较都会对其进行迭代。计算一次。

In the code snippet below you can see the main three sorting functions:

在下面的代码片段中,您可以看到主要的三个排序函数:

  • slowsort
    The accepted answer. Searches the position on every comparison.
  • mediumsort
    Improved slowsortby calculating the positions in advance
  • fastsort
    Improved mediumsortby avoiding searching alltogher.
  • slowsort
    接受的答案。在每次比较时搜索位置。
  • mediumsort
    改进slowsort通过计算提前位置
  • fastsort
    改进mediumsort避免搜索alltogher。

Note that these handle elements with an id not in given in the order by providing a fallback value of INF. If your order array matches the ids of the original array 1-to-1, then avoid sorting alltogether and just inserting the elements in the right position is the way to go. I added a function cheatsortthat does exactly that.

请注意,这些句柄元素的 id 未按顺序通过提供后备值INF。如果您的订单数组与原始数组的 id 1 对 1 匹配,则避免将所有元素排序在一起,只需将元素插入正确的位置即可。我添加了一个函数cheatsort来做到这一点。

You can more generally sort an array by a weight (see weightedsortin the example). Make sure to calculate the weight only once, to achieve good performance.

您可以更一般地按权重对数组进行排序(参见weightedsort示例)。确保只计算一次重量,以获得良好的性能。

Performance (for an array of length 1000)

性能(对于长度为 1000 的数组)

fastsort     about  1 ms
mediumsort   about  3 ms
slowsort     about 60 ms

Hint: For larger arrays the difference gets worse.

提示:对于较大的数组,差异变得更糟。

Sorting Function Comparison

排序功能比较

<?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');

回答by ManuA

This is how I sort my multi-dimensional array in ASC order based on idvalue:

这就是我如何根据id值按 ASC 顺序对多维数组进行排序:

$arrayFilter = array(
    array('product_tag' => 'xenia', 'id' => 4),
    array('product_tag' => 'worn',  'id' => 5),
    array('product_tag' => 'woven', 'id' => 3),
    array('product_tag' => 'nude', 'id' => 1)
);

for ($i = 0; $i < sizeof($arrayFilter); $i++) {
    for ($j=$i+1; $j < sizeof($arrayFilter); $j++) {
        if ($arrayFilter[$i]['id'] > $arrayFilter[$j]['id']) {
            $c = $arrayFilter[$i];
            $arrayFilter[$i] = $arrayFilter[$j];
            $arrayFilter[$j] = $c;
        }
    }
}
print_r($arrayFilter);

OUTPUT:

输出:

Array
(
    [0] => Array
        (
            [product_tag] => nude
            [id] => 1
        )

    [1] => Array
        (
            [product_tag] => woven
            [id] => 3
        )

    [2] => Array
        (
            [product_tag] => xenia
            [id] => 4
        )

    [3] => Array
        (
            [product_tag] => worn
            [id] => 5
        )
)