PHP - 排序字母数字键的更高效方法

4

注意:我编辑了示例并将所有值替换为1。 值无关紧要,只有键才重要。 我之前写的值导致了误解。 抱歉。

注意:a / b块始终是连续的。 我添加这个是因为不清楚。 如果有3a / 3b和5a / 5b,将始终有4a / 4b而不仅仅是4。

我有包含带前导零的编号键的数组。 有时,这些编号键具有我使用后缀a和b区分的两个变体。 具有或不具有变体的键的数量未知,但从不超过2位数; 即最高数字键为“09”。

问题在于,这些数组键需要按数字顺序排序,但当存在后缀时,应优先考虑这些后缀。 仅使用ksort()无法实现此目的。

例如,ksort()给我这个:

$arr = array(
    '01' => 1,
    '02' => 1,
    '03a' => 1,
    '03b' => 1,
    '04a' => 1,
    '04b' => 1,
    '05a' => 1,
    '05b' => 1,
    '06' => 1,
);

但是,我需要这个:
$arr = array(
    '01' => 1,
    '02' => 1,
    '03a' => 1,
    '04a' => 1,
    '05a' => 1,
    '03b' => 1,
    '04b' => 1,
    '05b' => 1,
    '06' => 1,
);

我使用了一些复杂的编码技巧来获得我想要的结果,但这并不美观。我想知道是否有更好、更干净的方法?


这是我的做法:

1)我使用ksort()函数,得到了上面两个数组中的第一个(还不是我想要的那个)。

2)我创建了两个数组,一个用于'a'后缀,另一个用于'b'后缀。


$arr_a = array();
$arr_b = array();

foreach ($arr as $k => $v) {
    if (substr($k, 2) == 'a') {
        $arr_a[$k] = $v;
    } else if (substr($k, 2) == 'b') {
        $arr_b[$k] = $v;
    }
}

3) 我合并了这两个后缀数组。

$arr_suffixes = array_merge($arr_a, $arr_b);

4) 我将原始数组切片,以获取后缀之前的部分和后缀之后的部分。

$i = array_search(key($arr_suffixes), array_keys($arr));
$length = count($arr_suffixes);

$arr_before_suffixes = array_slice($arr, 0, $i);
$arr_after_suffixes = array_slice($arr, $i + $length);

5) 使用array_merge函数,我重新组合切片后的数组,以创建所需的数组。

$arr = array_merge($arr_before_suffixes, $arr_suffixes);
$arr = array_merge($arr, $arr_after_suffixes);

最终,我们得到了正确的 $arr。有没有更好的方法来做这件事?感觉很丑陋。


不会发生这种情况。你要么有'04'键,要么同时拥有'04a'和'04b'键。注意:值并不重要,它们可能会影响下面的一些答案。只有键才是重要的。 - user3970381
@GeorgeGarchagudashvili ,但空格多还是少于字母a或b? - sectus
@derp - a/b块总是连续的。我应该提到这一点。所以,如果3a/3b和5a/5b,则始终会有4a/4b而不仅仅是4。 - user3970381
我有一个类似的排序,其中我使用uksort,在回调函数中使用sscanf拆分键来进行检查。 - Mark Baker
@Mark Baker - 谢谢。是的,看起来uksort正是我所需要的。 - user3970381
显示剩余2条评论
3个回答

3

您没有正式的规则。我会试着猜测。

$arr = array(
    '01' => 1,
    '02' => 1,
    '03a' => 1,
    '03b' => 1,
    '04a' => 1,
    '04b' => 1,
    '05a' => 1,
    '05b' => 1,
    '06' => 1,
);

uksort($arr, function($item1, $item2)
    {
    $last1 = substr($item1, -1);
    $last2 = substr($item2, -1);
    // one of the items is a number or last letters matches
    if (is_numeric($last1) || is_numeric($last2) || $last1 == $last2) 
        // simple number comparison
        return $item1 - $item2;
    else
        // natural order comparison
        return $last1 > $last2 ? 1 : -1;
    });

var_dump($arr);

我没有正式的规则?我有,我在上面详细解释了!谢谢,你的例子很好。这非常有帮助。 - user3970381
是的!我刚想到将你的usort转换为uksort。这太棒了。谢谢! - user3970381

2

natsort() 函数可以帮助您:

$arr = array(
    '01' => 1,
    '02' => 1,
    '03a' => 1,
    '03b' => 1,
    '04a' => 1,
    '04b' => 1,
    '05a' => 1,
    '05b' => 1,
    '06' => 1,
);
$keys = array_keys($arr);
natsort($keys);
$result = array();
foreach ($keys as $key) {
    $result[$key] = $arr[$key];
}
print_r($result); // Your expected result

这是错误的。你之所以“偶然”得到了预期的结果,是因为实际值而不是键。 - Smuuf
是的,我不应该像那样给出值。也许我应该改变我的例子?这些值可以是任何东西。 - user3970381
@derp - 编辑过了。这教会了我一个好教训,要做到精确。 - user3970381
@plutov.by 是的,就像derp所说,在你给出的例子中数组根本没有被改变。 - user3970381
我有一个非常不规则的键,除了这种方法之外,它无法通过任何其他方式进行排序。尝试了所有其他解决方案。感谢@plutov.by! - webaholik

2
$arr = array(
    '01' => 1,
    '02' => 2,
    '03a' => 3,
    '03b' => 6,
    '04a' => 4,
    '04b' => 7,
    '05a' => 5,
    '05b' => 8,
    '06' => 9,
);

uksort(
    $arr,
    function($a, $b) {
        sscanf($a, '%d%s', $an, $as);
        sscanf($b, '%d%s', $bn, $bs);
        if ($as === null || $bs === null || $as === $bs) {
            return $an - $bn;
        }
        return strcmp($as, $bs);
    }
);
var_dump($arr);

看起来比我的更干净。 - sectus
@sectus - 这仍然不会非常高效,因为需要为快速排序的每个比较执行回调;但它与您的方法并没有太大区别...只是使用sscanf代替substr,并使用strcmp而不是您的三元运算符。 - Mark Baker
你认为我在原始帖中的方法更有效吗? - user3970381
感谢您提供这个变体,看到不同的想法真是太好了。这确实有所帮助。 - user3970381
1
@Pierre - 这可能取决于你的数据量,使用uksort在处理小数据量时非常高效...但是在处理大数据量时,array_multisort可能会更好。 - Mark Baker
@Mark 对,说得有道理。非常感谢。 - user3970381

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