返回

PHP数组去重后索引不连续?3招教你完美解决

php

PHP 数组去重后,索引不连续怎么办?教你几招重新整理

写 PHP 代码处理数组时,array_unique() 是个挺方便的函数,能快速帮你干掉数组里重复的值。但用完之后,你可能会碰到一个有点别扭的情况:数组的索引(也就是键名)变得不连续了,中间可能会“断掉”。

遇到啥问题了?

举个例子,假设你手头有这么个数组:

$names = [
  0 => '张三',
  1 => '李四',
  2 => '张三',
  3 => '李四',
  4 => '王五',
  5 => '李四',
  6 => '张三',
  7 => '王五'
];

print_r($names);

这个数组里有不少重复的名字。咱们用 array_unique() 给它去个重:

$unique_names = array_unique($names);

print_r($unique_names);

执行之后,你会得到类似下面的结果:

Array
(
    [0] => 张三
    [1] => 李四
    [4] => 王五
)

看!重复的名字是没了,只剩下了 '张三', '李四', '王五'。但是,索引变成了 0, 1, 4。中间的 2 和 3 不见了!如果你期望得到的是一个像全新数组那样,索引从 0 开始连续递增的结果,比如下面这样:

Array
(
    [0] => 张三
    [1] => 李四
    [2] => 王五
)

array_unique() 的直接输出就达不到要求了。

为啥 array_unique 会留下“坑”?

这其实不是 array_unique 的 bug,而是它本身的设计特点。

array_unique() 函数的工作原理是:遍历原数组,保留每个值第一次出现时的键值对,后续再遇到相同的值就直接跳过。关键点在于 “保留第一次出现时的键值对”

在咱们那个例子里:

  • $names[0] 的值是 '张三',第一次出现,保留 [0] => '张三'
  • $names[1] 的值是 '李四',第一次出现,保留 [1] => '李四'
  • $names[2] 的值是 '张三',之前已经有过 '张三' 了,跳过。
  • $names[3] 的值是 '李四',之前已经有过 '李四' 了,跳过。
  • $names[4] 的值是 '王五',第一次出现,保留 [4] => '王五'
  • 后面的 '李四', '张三', '王五' 都重复了,全被跳过。

所以,最后留下来的是索引为 0, 1, 4 的元素。函数忠实地保留了它们在原数组中的“原始”索引。这种行为在处理关联数组,或者需要知道唯一值最初来源位置的场景下,是很有用的。但对于我们只想得到一个干净、索引连续的数值数组的需求来说,就得多加一步处理了。

几招搞定索引重排

知道了原因,解决起来就简单了。有几种常用的方法可以把这个带有“坑”的数组索引重新整理好。

方案一:使用 array_values() (推荐)

这是最直接、也最常用的方法。array_values() 函数的作用就是提取一个数组中所有的值,然后用这些值创建一个新的、索引从 0 开始连续递增的数值数组。

原理和作用:

array_values() 不关心输入数组的键是什么(不管是数字、字符串,还是像我们例子中那样不连续的数字),它只关注值。它会把所有值按顺序拿出来,放进一个全新的数组里,并且给这些值自动分配从 0 开始的连续数字索引。

代码示例:

$names = [
  0 => '张三',
  1 => '李四',
  2 => '张三',
  3 => '李四',
  4 => '王五',
  5 => '李四',
  6 => '张三',
  7 => '王五'
];

// 1. 先去重
$unique_names_with_gaps = array_unique($names);
echo "去重后,索引不连续:\n";
print_r($unique_names_with_gaps);
/*
输出:
Array
(
    [0] => 张三
    [1] => 李四
    [4] => 王五
)
*/

// 2. 使用 array_values() 重建索引
$reindexed_names = array_values($unique_names_with_gaps);
echo "\n使用 array_values() 重排索引后:\n";
print_r($reindexed_names);
/*
输出:
Array
(
    [0] => 张三
    [1] => 李四
    [2] => 王五
)
*/

一行代码 array_values($unique_names_with_gaps) 就解决了问题,非常清爽。

额外建议:

  • array_values() 非常安全,它不会修改原始数组 ($unique_names_with_gaps 保持不变),而是返回一个全新的数组。
  • 这是专门用来解决“只要值,重新编号”问题的理想函数,性能通常也很好。

进阶使用:

虽然这个场景很简单,但记住 array_values() 对任何类型的数组(索引数组、关联数组)都有效,它总是返回一个数值索引从 0 开始的数组。

方案二:使用 sort()rsort() (有副作用,需谨慎)

PHP 的排序函数,比如 sort() (升序) 和 rsort() (降序),在对数组进行排序的同时,也会丢弃原有的键名,并重新分配从 0 开始的连续数字索引。

原理和作用:

sort() 这类函数主要是用来排序的。但在排序过程中,对于数值索引的数组(或者它不关心的键),它会重建索引。不过,这会改变数组元素的顺序!

代码示例:

$names = [
  0 => '张三',
  1 => '李四',
  2 => '张三',
  3 => '李四',
  4 => '王五',
  5 => '李四',
  6 => '张三',
  7 => '王五'
];

$unique_names_with_gaps = array_unique($names); // [0 => '张三', 1 => '李四', 4 => '王五']

// 使用 sort() 尝试重排索引
// 注意:sort() 是直接修改原数组的,并且返回 true/false
sort($unique_names_with_gaps); // 按字母顺序(或本地化规则)排序

echo "使用 sort() 后:\n";
print_r($unique_names_with_gaps);
/*
输出可能会是(取决于你的环境和PHP版本对中文的排序规则):
Array
(
    [0] => 李四
    [1] => 王五
    [2] => 张三
)
或者其他顺序,但索引是 0, 1, 2
*/

重要提醒:

  • 强烈不推荐 仅仅为了重排索引而使用 sort()rsort()!除非你本来就希望在去重之后对结果进行排序。
  • 这些排序函数会直接修改传入的数组(原地操作),而不是返回新数组。
  • 元素的原始相对顺序会被打乱。在我们的例子里,'张三' 可能不再是第一个元素了。

安全建议:

  • 只有当你同时需要去重、排序、并且重排索引这三个效果时,才考虑在 array_unique 之后使用 sort()
  • 如果元素的顺序很重要,绝对不要用 sort() 来重排索引。请用 array_values()

进阶使用:

sort() 有不同的排序标记 (sort flags),比如 SORT_STRING, SORT_NUMERIC, SORT_NATURAL 等,可以影响排序行为。但无论哪种标记,它都会重置数值索引。如果你操作的是关联数组并希望保留键,需要用 asort() (值排序,保留键) 或 ksort() (键排序,保留键),但这两个函数不会重置索引。

方案三:手动循环创建新数组

如果不想用额外的内建函数,或者想在重排索引的过程中加入一些自定义逻辑,可以手动创建一个新数组。

原理和作用:

遍历经过 array_unique() 处理后的数组,然后把每个值依次添加到 一个新的空数组中。PHP 在向数组追加元素(不指定键名时,例如 $new_array[] = $value;)时,会自动使用从 0 开始的连续数字索引。

代码示例:

$names = [
  0 => '张三',
  1 => '李四',
  2 => '张三',
  3 => '李四',
  4 => '王五',
  5 => '李四',
  6 => '张三',
  7 => '王五'
];

$unique_names_with_gaps = array_unique($names); // [0 => '张三', 1 => '李四', 4 => '王五']

$reindexed_names_manual = []; // 创建一个空数组
foreach ($unique_names_with_gaps as $value) {
  $reindexed_names_manual[] = $value; // 将值追加到新数组
}

echo "手动循环重排索引后:\n";
print_r($reindexed_names_manual);
/*
输出:
Array
(
    [0] => 张三
    [1] => 李四
    [2] => 王五
)
*/

优点/适用场景:

  • 逻辑清晰,容易理解底层发生了什么。
  • 可以在 foreach 循环内部添加额外的处理逻辑(比如转换大小写、检查值等)。
  • 不需要记忆特定的函数名。

缺点:

  • 相比 array_values(),代码稍微冗长一点。
  • 对于非常大的数组,性能可能略逊于高度优化的内建函数 array_values()

进阶使用:

在循环中,你可以访问到原数组的键和值(foreach ($unique_names_with_gaps as $key => $value)),虽然在这个特定场景下我们只用到了 $value。这提供了更大的灵活性,比如你可能想基于原始键做某些判断,即使最终输出不需要这些键。

选哪个好?

对于“array_unique 之后重排索引,保持元素相对顺序不变”这个特定的问题:

  • 首选 array_values() :最简洁、意图最明确、性能通常也最优。它是专门为这种场景设计的。
  • 避免使用 sort() / rsort() :除非你明确需要排序,否则它们会带来改变元素顺序的副作用。
  • 手动循环 (foreach) :作为一个备选方案,或者当你需要在重排过程中嵌入其他逻辑时可以使用。

简单说,大多数情况下,用 array_values() 就对了!