对具有可选前导十进制值的字符串数组进行自然排序

问题描述 投票:0回答:2

我想按自然顺序对包含数字的数组进行排序,因此值较大的数字排在较小的数字之后,如下所示:

$numbers = array('10 Apple', '2 Grapes', '3 Apples', '3.2 Apples', '3.1 Apples', '3.3 Apples', '3.10 Apples', '3.11 Apples', 'Lots of Apples');
natsort($numbers);

这是我得到的结果,这不是我需要的结果,小数没有受到正确的威胁。

print_r($numbers);
Array
(
    [1] => 2 Grapes
    [2] => 3 Apples
    [4] => 3.1 Apples
    [3] => 3.2 Apples
    [5] => 3.3 Apples
    [6] => 3.10 Apples
    [7] => 3.11 Apples
    [0] => 10 Apple
    [8] => Lots of Apples
)

已经有一个类似的问题Array Sorting in php for Decimal Values但没有找到合适的答案。

属性排序的预期输出是

Array
(
    [1] => 2 Grapes
    [2] => 3 Apples
    [4] => 3.1 Apples
    [6] => 3.10 Apples
    [7] => 3.11 Apples
    [3] => 3.2 Apples
    [5] => 3.3 Apples
    [0] => 10 Apple
    [8] => Lots of Apples
)

所以我有点期望

natsort()
能做到这一点,但看起来它有问题,我必须自己实现类似的逻辑?这是正确的吗?

我正在考虑的一个解决方案是以某种方式将数字重新格式化为固定精度,并希望

natsort()
能够正常工作,但我想知道是否有更简单的解决方案或 PHP 内置的解决方案。

我尝试了https://github.com/awssat/numbered-string-order,这非常有趣,但也不支持小数。

php arrays sorting decimal natural-sort
2个回答
2
投票

我不是 100% 确定您的规格,所以请测试一下,但是

strnatcmp
似乎可以用来在
natsort
中运行
usort
变体。如果传递给比较器的两个字符串都以浮点数开头,则将它们转换为浮点数并使用飞船,否则默认为
strnatcmp

<?php

$numbers = ['10 Apple', '2 Grapes', '3 Apples', '3.2 Apples', '3.1 Apples', '3.3 Apples', '3.10 Apples', '3.11 Apples', 'Lots of Apples'];

usort($numbers, function ($a, $b) {
    if (preg_match("~^\d*\.\d+\b~", $a, $m)) {
        $aa = (float)$m[0];

        if (preg_match("~^\d*\.\d+\b~", $b, $m)) {
            $bb = (float)$m[0];
            return $aa <=> $bb;
        }
    }

    return strnatcmp($a, $b);
});
print_r($numbers);

0
投票

调用

sort($numbers, SORT_NUMERIC);
Demo 将尊重浮点值,甚至将连字符视为负数,但它会将无数字的值放置在数组的开头。还不够好。

为了对可选的前导浮点值进行稳定排序并在末尾定位无数字值,

usort()
就可以了。 演示

usort(
    $numbers,
    fn($a, $b) => (sscanf($a, '%f')[0] ?? PHP_INT_MAX)
                  <=>
                  (sscanf($b, '%f')[0] ?? PHP_INT_MAX)
);

为了最大限度地减少迭代函数调用的总数,您可以使用

array_multisort()
,但是如果有任何浮点值关系需要打破,这将对整个字符串进行二次排序。 演示

array_multisort(
    array_map(
        fn($v) => sscanf($v, '%f')[0] ?? PHP_INT_MAX,
        $numbers
    ),
    $numbers
);
© www.soinside.com 2019 - 2025. All rights reserved.