PHP数组去重后索引不连续?3招教你完美解决
2025-03-26 18:20:42
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()
就对了!