【问题标题】:Preserve key order (stable sort) when sorting with PHP's uasort使用 PHP 的 uasort 进行排序时保留键顺序(稳定排序)
【发布时间】:2011-05-20 05:14:37
【问题描述】:

这个问题实际上是受 SO 上的另一个问题启发的,我想稍微扩展一下。

在 PHP 中有一个关联数组是否可以对其值进行排序,但在值相等的情况下,可以使用 PHP 的一个(或多个)内置排序函数来保留原始键顺序?

这是我用来测试可能解决方案的脚本(还没有找到):

<?php
header('Content-type: text/plain');
for($i=0;$i<10;$i++){
    $arr['key-'.$i] = rand(1,5)*10;
}
uasort($arr, function($a, $b){
    // sort condition may go here //
    // Tried: return ($a == $b)?1:($a - $b); //
    // Tried: return $a >= $b; //
});
print_r($arr);
?>

陷阱:因为键是在原始数组中排序的,所以请不要试图建议任何按键排序以恢复到原始顺序。我用它们制作了示例,以便更容易在输出中直观地检查它们的顺序。

【问题讨论】:

  • 换句话说,这个问题的解决方案是一种稳定的排序算法,而PHP的排序算法表面上都不是。
  • 我很怀疑,但我想要一个明确的答案和/或可能的解决方法。
  • 是否有理由只使用 biult-in 功能?
  • php.net/manual/en/array.sorting.php - 如果这些排序函数中的任何一个计算两个成员相等,则顺序未定义(排序不稳定)。
  • 请看一下:notmysock.org/blog/php/schwartzian-transform.html 它解决了我的问题。

标签: php arrays algorithm sorting


【解决方案1】:

由于PHP does not support stable sort after PHP 4.1.0,需要自己编写函数。

这似乎符合您的要求:http://www.php.net/manual/en/function.usort.php#38827

正如手册所说,“如果两个成员比较相等,则它们在排序数组中的顺序是未定义的。”这意味着使用的排序不是“稳定的”,并且可能会更改比较相等的元素的顺序。

有时你确实需要一个稳定的排序。例如,如果您按一个字段对列表进行排序,然后按另一个字段再次对其进行排序,但不想丢失前一个字段的排序。在这种情况下,最好将 usort 与将两个字段都考虑在内的比较函数一起使用,但如果您不能这样做,请使用下面的函数。它是一种合并排序,保证了 O(n*log(n)) 复杂度,这意味着即使您使用更大的列表,它也能保持相当快的速度(不像冒泡排序和插入排序,它们是 O(n^2))。

<?php
function mergesort(&$array, $cmp_function = 'strcmp') {
    // Arrays of size < 2 require no action.
    if (count($array) < 2) return;
    // Split the array in half
    $halfway = count($array) / 2;
    $array1 = array_slice($array, 0, $halfway);
    $array2 = array_slice($array, $halfway);
    // Recurse to sort the two halves
    mergesort($array1, $cmp_function);
    mergesort($array2, $cmp_function);
    // If all of $array1 is <= all of $array2, just append them.
    if (call_user_func($cmp_function, end($array1), $array2[0]) < 1) {
        $array = array_merge($array1, $array2);
        return;
    }
    // Merge the two sorted arrays into a single sorted array
    $array = array();
    $ptr1 = $ptr2 = 0;
    while ($ptr1 < count($array1) && $ptr2 < count($array2)) {
        if (call_user_func($cmp_function, $array1[$ptr1], $array2[$ptr2]) < 1) {
            $array[] = $array1[$ptr1++];
        }
        else {
            $array[] = $array2[$ptr2++];
        }
    }
    // Merge the remainder
    while ($ptr1 < count($array1)) $array[] = $array1[$ptr1++];
    while ($ptr2 < count($array2)) $array[] = $array2[$ptr2++];
    return;
}
?>

另外,您可能会发现this forum thread 很有趣。

【讨论】:

  • 论坛主题不有趣。其他部分或您的答案是。
  • @Alin,这就是我写的原因,“可能觉得它很有趣......”:)
  • @Alin,实际上得出的结论是,其中描述的所有方法都不起作用。所以,在某种程度上,我们知道不要尝试这些方法。从这个意义上说很有帮助。
  • 我认为你应该注明你的来源。我发现这个方法重复了here。
  • 请看@Martijn 的回答,它使用了装饰,uasort 带有一个包装的比较函数,并且需要 1/5 的时间作为这个答案(这是一个很好的说明,但没有效率)。跨度>
【解决方案2】:

为了完整起见,您还应该查看Schwartzian transform:

// decorate step
$key = 0;
foreach ($arr as &$item) {
        $item = array($item, $key++); // add array index as secondary sort key
}

// sort step
asort($arr); // sort it

// undecorate step
foreach ($arr as &$item) {
    $item = $item[0]; // remove decoration from previous step
}

PHP 的默认排序算法适用于数组,原因如下:

array(1, 0) < array(2, 0); // true
array(1, 1) < array(1, 2); // true

如果您想使用自己的排序标准,您也可以使用uasort():

// each parameter is an array with two elements
// [0] - the original item
// [1] - the array key
function mysort($a, $b)
{
    if ($a[0] != $b[0]) {
        return $a[0] < $b[0] ? -1 : 1;
    } else {
        // $a[0] == $b[0], sort on key
        return $a[1] < $b[1] ? -1 : 1; // ASC
    }
}

【讨论】:

  • 请注意问题中的陷阱。您不应该依赖在原始数组中排序的键。
  • @AlinPurcaru 好的,很公平。已编辑:)
【解决方案3】:

array_multisort 派上用场,只需使用有序范围作为第二个数组($order 只是临时的,它用于按原始顺序对第一个数组的等效项进行排序):

$a = [
  "key-0" => 5,
  "key-99" => 3,
  "key-2" => 3,
  "key-3" => 7
];

$order = range(1,count($a));
array_multisort($a, SORT_ASC, $order, SORT_ASC);

var_dump($a);

输出

array(4) {
  ["key-99"]=>
  int(3)
  ["key-2"]=>
  int(3)
  ["key-0"]=>
  int(5)
  ["key-3"]=>
  int(7)
}

我使用了带有未排序键的测试数据来证明它可以正常工作。尽管如此,这是您的测试脚本的输出:

Array
(
    [key-1] => 10
    [key-4] => 10
    [key-5] => 20
    [key-8] => 20
    [key-6] => 30
    [key-9] => 30
    [key-2] => 40
    [key-0] => 50
    [key-3] => 50
    [key-7] => 50
)

缺点

它仅适用于预定义的比较,您不能使用自己的比较功能。可能的值(array_multisort() 的第二个参数)是:

排序类型标志:

  • SORT_ASC - 升序排列项目。
  • SORT_DESC - 对项目进行降序排序。
  • SORT_REGULAR - 正常比较项目(不要更改类型)
  • SORT_NUMERIC - 数字比较项目
  • SORT_STRING - 将项目作为字符串进行比较
  • SORT_LOCALE_STRING - 根据当前语言环境将项目作为字符串进行比较。它使用语言环境,可以使用更改 setlocale()
  • SORT_NATURAL - 使用“自然排序”将项目作为字符串进行比较,例如 natsort()
  • SORT_FLAG_CASE - 可以与 SORT_STRING 或 SORT_NATURAL 组合(按位或)对字符串进行不区分大小写的排序

【讨论】:

  • 您也可以使用array_sort($a, SORT_ASC, array_keys($a), SORT_NATURAL) 进行类似形式的稳定排序。将 [ 'Sick' =&gt; 8, 'Vacation' =&gt; 12, 'Other' =&gt; -4, 'Holiday' =&gt; 0, 'Bereavement' =&gt; 0 ] 更改为 [ 'Other' =&gt; -4, 'Bereavement' =&gt; 0, 'Holiday' =&gt; 0, 'Sick' =&gt; 8, 'Vacation' =&gt; 12 ]
【解决方案4】:

只是用一些非常具体的案例来完成回复。如果$array的数组键是默认的,那么一个简单的array_values(asort($array))就足够了(这里以升序为例)

【讨论】:

    【解决方案5】:

    为了将来参考,我在 Github 上放了一组内置 PHP 函数的稳定排序变体:https://github.com/vanderlee/PHP-stable-sort-functions,基于 @Jack 的解决方案和其他一些技巧。

    【讨论】:

      【解决方案6】:

      这是一个可以在 usort 函数中实现稳定排序的解决方案

      public function sortBy(array &$array, $value_compare_func)
      {
          $index = 0;
          foreach ($array as &$item) {
              $item = array($index++, $item);
          }
          $result = usort($array, function($a, $b) use ($value_compare_func) {
              $result = call_user_func($value_compare_func, $a[1], $b[1]);
              return $result == 0 ? $a[0] - $b[0] : $result;
          });
          foreach ($array as &$item) {
              $item = $item[1];
          }
          return $result;
      }
      

      【讨论】:

        【解决方案7】:

        作为稳定排序的解决方法:

        <?php
        header('Content-type: text/plain');
        for ($i = 0;$i < 10;$i++)
        {
            $arr['key-' . $i] = rand(1, 5) * 10;
        }
        uksort($arr, function ($a, $b) use ($arr)
        {
            if ($arr[$a] === $arr[$b]) return array_search($a, array_keys($arr)) - array_search($b, array_keys($arr));
            return $arr[$a] - $arr[$b];
        });
        print_r($arr);
        

        【讨论】:

        • 从时间复杂度的角度来看,比较器中的 4x 嵌套 array_search/array_keys 调用确实受到了伤害。 This answer 本质上是先对排序的索引进行一次编码,然后将它们剥离,之后都是线性操作,不会破坏底层的复杂性。另一方面,如此处所示为每次比较遍历数组会将其提高到 O(n * n * log(n)) - 比冒泡排序更差,但如果您只有几个元素,则很好。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-04-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-12
        • 1970-01-01
        • 2013-10-29
        相关资源
        最近更新 更多