按数字值对一个扁平的关联数组进行排序,然后再按非数字键进行排序。

29
我有一个字符串键的数组,其中包含数字值,用于创建一个标记列表,每个标记的出现次数如下所示:
$arrTags = [
    'mango' => 2, 
    'orange' => 4, 
    'apple' => 2,
    'banana' => 3
];

我想展示标签的列表,按照降序排列值,然后按照升序排列标签名,以便产生:

orange (4)  
banana (3) 
apple (2) 
mango (2)

arsort()不适合,因为它会把mango放在apple之前。我猜usort()可能是正确的方法,但我在php.net的评论中没有找到合适的示例。

8个回答

42

正如Scott Saunders在他对David解决方案的评论中所暗示的那样,您可以使用array_keys()和array_values()函数来摆脱循环。实际上,您可以用一行代码解决这个问题:

array_multisort(array_values($arrTags), SORT_DESC, array_keys($arrTags), SORT_ASC, $arrTags);

感谢您提供的代码。我遇到了与原问题完全相同的情况,这对我很有帮助。让我困扰的是,我不太确定这里到底发生了什么。最后一个参数的作用是什么?我可以看出,最后一个参数(通过引用传递)是返回已更改数组的唯一可能性,没错。但是,这些数组集如何相互关联呢?当多重排序从左到右进行时,随后的数组是否也会一起排序?如果是这样,则所有数组必须具有相同的长度,而这并非文档所要求。#困惑 - andypotter
@andypotter:基本上,array_multi_sort()将在内部创建另一个数组$indirect,其中每个索引都是包含所提供数组的所有值的数组:$indirect[$i] = [$v1,$v2,$v3,...,NULL]。然后使用特殊比较函数对数组应用快速排序,该函数将首先比较$indirect[$a][$r]$indirect[$b][$r],其中$r==0如果它们相等,则增加$r直到$indirect[$a][$r]null(已使用所有提供的数组)。最后,根据$indirect重写每个数组。如果您使用不同大小的数组,则返回FALSE。 - 2072
@dearsina:说得好。然而,OP特别提到了字符串键。事实上,他要求“任何具有相同数值的标签...按字母顺序排序”。 - Jon Bernhardt

15

已解决

经过一些实验,我发现 array_multisort 可以很好地解决这个问题:

$tag = array(); 
$num = array();

foreach($arrTags as $key => $value){ 
$tag[] = $key; 
$num[] = $value; 
}

array_multisort($num, SORT_DESC, $tag, SORT_ASC, $arrTags);

:)


2
那应该是SlappyTheFish的答案,对吧?我认为你应该将那个答案标记为已接受,然后查看array_keys()和array_values()函数以摆脱循环。 - Scott Saunders
4
未来的访问者请注意:$tag = array_keys( $arrTags); $num = array_values( $arrTags); 可以在不使用循环的情况下形成相同的数组。 - nickb
为什么这对我不起作用?我复制了上面的示例和解决方案,但在问题中得到了相同的结果。 - Tim Yao
@TimYao,你的键是数字吗?https://3v4l.org/uUYUO - dearsina

15
请看第三个例子: http://php.net/manual/en/function.array-multisort.php 你需要创建两个数组来用作索引;一个由原始数组的键组成,另一个由原始数组的值组成。
然后使用multisort按文本值(原始数组的键)和数字值(原始数组的值)排序。

6
那么,什么是不复杂的解决方案? - Déjà vu
2
请参考下面Jon Bernhardt提供的优秀实现示例。 - d.raev

6

之前提出的解决方案看起来很合理,但实际上并不起作用:

ksort($arrTags);
arsort($arrTags);

实现所需排序的完整PHP代码如下:

$k = array_keys($arrTags);
$v = array_values($arrTags);
array_multisort($k, SORT_ASC, $v, SORT_DESC);
$arrTags = array_combine($k, $v);

请注意,array_multisort() 在用户输入上使用引用,因此您需要使用两个临时变量 ($k 和 $v) 作为用户输入提供内容。这样,array_multisort() 可以更改内容。稍后,通过 array_combine() 重新构建已排序的数组。
我已经编写了一个可重复使用的函数来完成此任务:
<?php
/**
 * Sort a multi-dimensional array by key, then by value.
 *
 * @param array Array to be sorted
 * @param int One of the available sort options: SORT_ASC, SORT_DESC, SORT_REGULAR, SORT_NUMERIC, SORT_STRING
 * @param int One of the available sort options: SORT_ASC, SORT_DESC, SORT_REGULAR, SORT_NUMERIC, SORT_STRING
 * @return void
 * @example The following array will be reordered:
 *  $a = array(
 *      'd' => 4,
 *      'c' => 2,
 *      'a' => 3,
 *      'b' => 1,
 *      'e' => 2,
 *      'g' => 2,
 *      'f' => 2,
 *  );
 *  SortArrayByKeyThanValue($a);        # reorder array to: array(
 *      'b' => 1,
 *      'c' => 2,
 *      'e' => 2,
 *      'f' => 2,
 *      'g' => 2,
 *      'a' => 3,
 *      'd' => 4,
 *  );
 * @author Sijmen Ruwhof <sijmen(a)secundity.com>
 * @copyright 2011, Secundity
 */
function SortArrayByKeyThanValue (&$pArray, $pSortMethodForKey = SORT_ASC, $pSortMethodForValue = SORT_ASC)
{
    # check user input: sorting is not necessary
    if (count($pArray) < 2)
        return;

    # define $k and $v as array_multisort() needs real variables, as user input is put by reference
    $k = array_keys  ($pArray);
    $v = array_values($pArray);

    array_multisort(
        $v, $pSortMethodForValue,
        $k, $pSortMethodForKey
    );
    $pArray = array_combine($k, $v);
}
?>

这个答案显然是错误的:https://3v4l.org/NsbqN 你看它没有按值排序吗?这个答案实际上和 ksort() 排序是一样的。 - mickmackusa

3

SlappyTheFish是正确的,使用array_multisort而不是ksort、arsort。

在David的例子中,ksort、arsort可以正常工作,但是如果键的字符串值包含除字母以外的字符,则排序可能无法按预期进行。

例如:

$arrTags['banana'] = 3;
$arrTags['mango'] = 2;
$arrTags['apple1'] = 2;
$arrTags['orange'] = 4;
$arrTags['almond1'] = 2;

ksort($arrTags);
arsort($arrTags);

print_r($arrTags);

返回值:

Array
(
    [orange] => 4
    [banana] => 3
    [apple1] => 2
    [mango] => 2
    [almond1] => 2
)

然而,使用:

$arrTags['banana'] = 3;
$arrTags['mango'] = 2;
$arrTags['apple1'] = 2;
$arrTags['orange'] = 4;
$arrTags['almond1'] = 2;

$tag = array();
$num = array();

foreach($arrTags as $key => $value){
    $tag[] = $key;
    $num[] = $value;
}

array_multisort($num, SORT_DESC, $tag, SORT_ASC, $arrTags);


print_r($arrTags);

返回:

Array
(
    [orange] => 4
    [banana] => 3
    [almond1] => 2
    [apple1] => 2
    [mango] => 2
)

这个答案是误导性的,而且已经不再适用。从PHP7及以上版本开始,ksort() then arsort()将正确地对数组进行排序。在PHP7之前的排序算法使得这种技术不可靠。重要的是,这种不同的行为与键中存在的数字无关。请参见此演示:https://3v4l.org/d3a1m。请更新您的答案以使其正确和最新。目前,您答案中唯一正确/真实的部分是从https://dev59.com/q3E95IYBdhLWcg3whOHK#2282247复制的。 - mickmackusa
你的后面一段代码可以更简洁:https://3v4l.org/8UcaZ,甚至更简洁,不使用循环:https://3v4l.org/pXl20。 - mickmackusa

1
//preserve arrays keys for later use
$ar1= array_keys($your_array);

//preserve array's values for later use
$ar2= array_values($your_array);

//perform sorting by value and then by key
array_multisort($ar2, SORT_DESC, $ar1, SORT_DESC);

//combine sorted values and keys arrays to new array
$sorted_array = array_combine($ar1, $ar2);

必须是ok的。

这是唯一可行的解决方案,如果键是数字的话。https://3v4l.org/uUYUO - dearsina

1

使用uksort()将键传递到自定义函数的作用域中;在该作用域内,通过使用传入(完整)数组上的键来访问关联值。

这种方法的优点在于时间复杂度--这比两个单独的排序函数调用更直接,并且不需要设置array_multisort()。此外,array_multisort()将销毁数字键(尽管提问者的键不是数字键)https://3v4l.org/rQak4

虽然在此问题提出时还没有太空船(三路)运算符可用,但现在它已经可用了,它使比较更加容易/清晰。

从PHP7.4开始,语法非常简洁。(演示

uksort($arrTags, fn($a, $b) => [$arrTags[$b], $a] <=> [$arrTags[$a], $b]);

从PHP7.0到PHP7.3,您必须使用use()来传递主数组。(演示)

uksort(
    $arrTags,
    function($a, $b) use ($arrTags) {
        return [$arrTags[$b], $a] <=> [$arrTags[$a], $b];
    }
);

0

你想得太复杂了:

ksort($arrTags);
arsort($arrTags);

现在你的数组已经按照你想要的方式排序了。

注意:这种技术只在PHP7及以上版本中可靠:https://3v4l.org/ma7ab


我正要发布这个确切的答案。无论如何,我已经尝试过了,可以确认它是有效的。 - Michael Mior
4
PHP的排序算法不是稳定的,因此不能保证这种方法一定可行。参考链接:http://www.php.net/manual/en/array.sorting.php - goat
@chris,这是真的,但我还没有找到任何它不能工作的情况,所以我会继续使用它。 - Tatu Ulmanen
5
谢谢你的想法,但对我来说行不通。它成功地将数字按降序排列,但相同数值的关键排序相当随机,肯定不是按字母顺序排序的。虽然不确定原因。 - David

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接