返回

PHP 数组特定对象稳定排序:移动至末尾技巧

php

PHP 中将数组中符合特定条件的对象移动到末尾

在处理数组数据时,有时我们需要根据特定条件调整元素的位置。 比如,给你一个对象数组,需要把含有特定属性值的对象移到数组最后。这种操作在数据处理和排序中很常见。这篇东西讲讲,如何在PHP里实现这一需求,并保持其他元素的相对顺序不变(稳定排序)。

问题

我们有一个对象数组,每个对象都有一个 pagerank 属性。 我们的目标是:找出 pagerank 值为 'R' 的对象,并把它放到数组的最后,同时保证其他对象的原有顺序。

初始数组:

[
    (object) ['pagerank' => 3],
    (object) ['pagerank' => 1],
    (object) ['pagerank' => 'R'],
    (object) ['pagerank' => 2],
    (object) ['pagerank' => 7]
]

期望结果:

[
    (object) ['pagerank' => 3],
    (object) ['pagerank' => 1],
    (object) ['pagerank' => 2],
    (object) ['pagerank' => 7]
    (object) ['pagerank' => 'R'],
]

问题分析

这个问题可以理解为一个特殊的排序问题。 常规的排序函数(如 sort()usort())可能会改变其他元素的相对位置。 我们需要的是一种“稳定”的移动方法:找到目标对象,把它从当前位置“抠”出来,再“粘”到数组末尾。其他的对象就像填空一样,往前挪一个位置就好。

解决方案

方案一:使用 array_filter 和数组合并

这个方法分几步走:

  1. 筛选:array_filter 分别找出 pagerank 不为 'R' 的对象和 pagerank'R' 的对象。
  2. 合并:array_merge 将两个筛选结果合并,'R' 对象在后。

原理: array_filter 可以根据提供的回调函数来筛选数组元素。 我们用两次 array_filter,一次保留非 'R' 对象,一次保留 'R' 对象。最后合并就得到了想要的结果。

代码示例:

<?php

$array = [
    (object) ['pagerank' => 3],
    (object) ['pagerank' => 1],
    (object) ['pagerank' => 'R'],
    (object) ['pagerank' => 2],
    (object) ['pagerank' => 7]
];

$notR = array_filter($array, function ($obj) {
    return $obj->pagerank != 'R';
});

$isR = array_filter($array, function ($obj) {
    return $obj->pagerank == 'R';
});

$result = array_merge($notR, $isR);

print_r($result);

?>

注意: array_filter 默认保留原始键名。 如果想重新索引数组,可以在合并后使用 array_values

$result = array_values(array_merge($notR, $isR));

方案二: 遍历 + 删除 + 添加

这种方法更直接:

  1. 遍历: 遍历数组,找到 pagerank'R' 的对象。
  2. 删除:unset 从数组中删除该对象。
  3. 添加:array_push 将该对象添加到数组末尾。

原理: 遍历找到目标后,unset 删除元素,这不会影响其他元素的索引。array_push 在末尾添加元素, 完成移动。

代码示例:

<?php

$array = [
    (object) ['pagerank' => 3],
    (object) ['pagerank' => 1],
    (object) ['pagerank' => 'R'],
    (object) ['pagerank' => 2],
    (object) ['pagerank' => 7]
] ;

foreach ($array as $key => $obj) {
    if ($obj->pagerank == 'R') {
        $target = $obj;
        unset($array[$key]);
        $array[] = $target; // 简写, 等同于 array_push($array, $target);
        break; // 假设只有一个 'R' 对象,找到后就停止
    }
}

print_r($array);

?>

安全性提示: 如果你的数组里可能有多个pagerank值为'R'的对象, 想把它们都移到末尾, 就不要用break。 代码会继续遍历, 把所有'R'对象都挪到最后.

方案三: 使用 usort 进行稳定排序 (进阶)

尽管前面提到 usort 通常用于完全排序, 但我们也可以通过巧妙的比较函数, 实现稳定排序并移动目标对象。

原理: 我们定义一个比较函数,对'R'和其他值区别对待:

  • 如果两个值都不是'R',保持原样(返回0)。
  • 如果一个是'R',另一个不是,把'R'排在后面(根据'R'的位置返回1或-1)。
  • 如果两个都是'R',也保持原样.

代码:

<?php

$array = [
    (object) ['pagerank' => 3],
    (object) ['pagerank' => 1],
    (object) ['pagerank' => 'R'],
    (object) ['pagerank' => 2],
    (object) ['pagerank' => 7]
];

usort($array, function ($a, $b) {
    if ($a->pagerank == 'R' && $b->pagerank != 'R') {
        return 1;
    } elseif ($a->pagerank != 'R' && $b->pagerank == 'R') {
        return -1;
    } else {
        return 0;
    }
});
print_r($array);
?>

进阶用法--处理多个特殊值

假设除了'R'之外, 你还想把'pagerank'为'S'的也移到末尾, 并且'S'要在'R'的前面. 怎么做?

修改方案二的遍历逻辑,或者修改方案三 usort 的比较函数,增加对'S'的处理即可。例如, 对于usort:

<?php

$array = [
    (object) ['pagerank' => 3],
    (object) ['pagerank' => 1],
    (object) ['pagerank' => 'R'],
    (object) ['pagerank' => 2],
      (object) ['pagerank' => 'S'],
    (object) ['pagerank' => 7]
];

usort($array, function ($a, $b) {
  $order = ['R' => 2, 'S' => 1]; //定义特殊值的顺序

    $aOrder = $order[$a->pagerank] ?? 0;
    $bOrder = $order[$b->pagerank] ?? 0;

    if ($aOrder != $bOrder)
    {
         return  $aOrder - $bOrder; //有特殊标记的排后面
    }
    else
    {
        return 0;  //顺序不变.
    }
});
print_r($array);
?>

总结

上面提供了三种 PHP 方法,实现将对象数组中符合特定条件的对象移动到末尾,同时保持数组稳定。根据具体情况和代码风格,你可以灵活选择。方法一最直观,方法二最高效, 方法三更灵活. 这些技巧也可以用在其它类似场景, 对数据进行特定调整。