PHP:检查数组是否有重复项

90

我知道这是一个非常显而易见的问题,而且肯定有一个可以做到这一点的函数,但我似乎找不到它。在PHP中,我想知道我的数组是否存在重复项,尽可能高效地实现。我不想像array_unique那样删除它们,也不特别想运行array_unique并将其与原始数组进行比较以查看它们是否相同,因为这似乎非常低效。就性能而言,“预期条件”是数组没有重复项。

我只想能够做类似这样的事情:

if (no_dupes($array))
    // this deals with arrays without duplicates
else
    // this deals with arrays with duplicates

有没有一些明显的函数我没想到?
如何检测 PHP 数组中的重复值?
这个问题的标题是正确的,而且是一个非常类似的问题,但是如果你实际上阅读了问题,他正在寻找 array_count_values。


你只是想知道是否有重复项,还是要知道重复项的数量和值等相关信息? - delete me
1
我只需要知道是否有任何重复项。返回一个布尔值就可以了。 - Mala
23
老实说,我认为if(count($array) == count(array_unique($array)))是最好的选择。你必须以这种或那种方式遍历数组,而我认为内置函数已经对此进行了优化。array_flip也可以考虑使用。 - Felix Kling
@Mike Sherov:你确定吗?我找不到任何关于它的信息,但我希望PHP数组有一些内部属性来跟踪长度。你有相关信息吗?我会非常感兴趣。 - Felix Kling
@Felix,我一直被教导在PHP中计数是一个昂贵的操作,需要循环。也许这是错误的。 - Mike Sherov
显示剩余2条评论
17个回答

256
我知道你不是在寻找array_unique()。然而,你将无法找到一个神奇的显而易见的函数,编写这样一个函数也比使用原生函数更快。
我的建议是:
function array_has_dupes($array) {
   // streamline per @Felix
   return count($array) !== count(array_unique($array));
}

根据您的比较需求,调整array_unique() 的第二个参数。


3
谢谢建议。我认为要找到更好的算法,技术上讲,一旦你运行内置的 array_unique 函数,你应该能知道是否有重复项。因此,任何做的工作至少与 array_unique 一样多的东西都比必要的多。尽管是这样,如果不存在这样的函数,我也不想特别写一个。 - Mala
1
如果你只关心它是否有重复项,那么这就是我会做的。如果你关心的不仅仅是它是否有重复项,那么你是对的,上面的方法可能会比它需要的更多地工作。无论你写什么,它都将是O(n^2)。即使你提前退出。正如你所说,你很少有重复项。那么,值得你花时间去创造一些神奇的东西吗? - Jason McCreary
1
我来这里只是为了找到确切的答案 :) - Gino Pane
你应该给我们一些线索,说明第二个参数的作用。 - aksu
5
优雅,但array_unique有点慢。如果您知道数组只包含整数和字符串,可以用array_flip替换它,以获得更快的结果。 - Tgr
显示剩余6条评论

105

性能优化解决方案

如果您关心性能和微观优化,请检查此一行代码:

function no_dupes(array $input_array) {
    return count($input_array) === count(array_flip($input_array));
}

描述:
该函数比较$input_array中的数组元素数与array_flip的元素数。值变为键,而在关联数组中,键必须是唯一的,因此不唯一的值将丢失,最终元素数比原始元素数低。

警告:
手册所述,数组键只能是类型为intstring,因此您必须在原始数组值中具有相同的类型以进行比较,否则PHP将开始进行强制类型转换并产生意外结果。请参见https://3v4l.org/7bRXI的示例以了解此异常情况的失败模式。

针对包含1000万条记录的数组的证明:

测试用例:

<?php

$elements = array_merge(range(1,10000000),[1]);

$time = microtime(true);
accepted_solution($elements);
echo 'Accepted solution: ', (microtime(true) - $time), 's', PHP_EOL;

$time = microtime(true);
most_voted_solution($elements);
echo 'Most voted solution: ', (microtime(true) - $time), 's', PHP_EOL;

$time = microtime(true);
this_answer_solution($elements);
echo 'This answer solution: ', (microtime(true) - $time), 's', PHP_EOL;

function accepted_solution($array){
 $dupe_array = array();
 foreach($array as $val){
  // sorry, but I had to add below line to remove millions of notices
  if(!isset($dupe_array[$val])){$dupe_array[$val]=0;}
  if(++$dupe_array[$val] > 1){
   return true;
  }
 }
 return false;
}

function most_voted_solution($array) {
   return count($array) !== count(array_unique($array));
}

function this_answer_solution(array $input_array) {
    return count($input_array) === count(array_flip($input_array));
}

请注意,在巨大数组的开头存在非唯一值时,被认可的解决方案在某些情况下可能会更快。


你能解释一下为什么这个更快吗?而且这个返回相反的结果。所以为了公平比较,你应该使用以下代码进行测试:function most_voted_solution($array) { return count($array) === count(array_unique($array)); } - Erdal G.
2
@ErdalG。这个更快,因为array_flip用C语言编写的本地PHP函数,而flip操作非常简单。翻转后,非唯一值被删除,因为它们可能会创建数组键冲突。 - s3m3n

41

您可以这样做:

function has_dupes($array) {
    $dupe_array = array();
    foreach ($array as $val) {
        if (++$dupe_array[$val] > 1) {
            return true;
        }
    }
    return false;
}

7
我喜欢!只需记住,即使有早期的“return”,这仍然是一个O(n)的函数。除了“foreach”和跟踪“$dupe_array”的开销外,我想看到一些基准测试结果。我猜对于没有重复项的数组,利用本地函数会更快。绝对比O(n ^ 2)好。不错。 - Jason McCreary
2
有一个小问题:只有在值为字符串或数字时才能正常工作。 - Artefacto
10
这段代码在PHP中出现了“undefined offset”错误。我改成了:foreach ($a as $v) { if (array_key_exists($v, $dupe)) { return true; } else { $dupe[$v] = true; } - EleventyOne
3
这到底是怎么回事?由于$dupe_array没有被赋予任何值,$dupe_array[$val]应该返回未定义的索引! - Nikunj Madhogaria
++dupe_array[$val] 是什么意思?难道不应该是 ++dupe_arrays[$key] 吗?因为 $val 不等于 $key。 - Salam.MSaif
++dupe_array[$val] 的想法是计算每个数组值出现的次数。但由于这些值没有初始化,因此在 PHP 7.1 中需要注意(notices)。但是,如果您想保持计数行为,则可以通过在当前的 if 块之前添加 if (!isset($dupe_array[$val])) {$dupe_array[$val] = 0} 来修复代码。 - Milania

23
$hasDuplicates = count($array) > count(array_unique($array)); 

如果有重复则为true,如果没有重复则为false


这基本上是对@JasonMcCreary答案的重复。 https://dev59.com/GHA75IYBdhLWcg3wuLjD#3145647 - mickmackusa
但是即使数组中有空值,它仍然会抛出重复值错误。我已经在下面发布了答案 https://dev59.com/GHA75IYBdhLWcg3wuLjD#67122587 - Prasad Patel

6
$duplicate = false;

 if(count(array) != count(array_unique(array))){
   $duplicate = true;
}

1
这基本上是对@JasonMcCreary答案的重申。https://dev59.com/GHA75IYBdhLWcg3wuLjD#3145647 - mickmackusa

5
这是我的看法...经过一些基准测试,我发现这是最快的方法。
function has_duplicates( $array ) {
    return count( array_keys( array_flip( $array ) ) ) !== count( $array );
}

根据情况,这可能会稍微快一些。

function has_duplicates( $array ) {
    $array = array_count_values( $array );
    rsort( $array );
    return $array[0] > 1;
}

2
不确定为什么你的答案中需要使用 array_keys()。如果值相同,array_flip() 已经压缩了数组。此外,由于类型在 count() 中本质上是相同的(你提到过基准测试),因此 != 是足够的比较器。因此,return count(array_flip($arr)) != count($arr); 应该就足够了。 - cartbeforehorse
这个答案中的技术与@s3m3n的函数具有相同的漏洞。https://3v4l.org/3FlBJ 这是一个“苹果对橙子”的比较,因此我会认为任何基准比较都是不适当的,因为这些函数并没有提供相同的行为。 - mickmackusa

1
保持简单,傻瓜!;)
简单的逻辑...
function checkDuplicatesInArray($array){
    $duplicates=FALSE;
    foreach($array as $k=>$i){
        if(!isset($value_{$i})){
            $value_{$i}=TRUE;
        }
        else{
            $duplicates|=TRUE;          
        }
    }
    return ($duplicates);
}

敬礼!


3
#BadCode - 最好使用PHP自身的函数来进行这种检查。 - FabianoLothor
我认为变量变量通常不是一个好的解决方案。这种技术在某些情况下可能会失败。https://3v4l.org/kGLWT 此外,从PHP7.4开始。 - mickmackusa

1
为了从比较中删除所有空值,您可以添加array_diff()
if (count(array_unique(array_diff($array,array("")))) < count(array_diff($array,array(""))))

参考自@AndreKR的回答,链接在这里


0

我正在使用这个:

if(count($array)==count(array_count_values($array))){
    echo("all values are unique");
}else{
    echo("there's dupe values");
}

我不知道它是否是最快的,但到目前为止它运行得相当不错


某些数据类型会导致此技术失败,因此这不是一种可靠/健壮的解决方案。https://3v4l.org/FSr7P - mickmackusa

0

我能想到的两种高效方法如下:

  1. 将所有值插入某种哈希表中,并检查您要插入的值是否已经在其中(预期时间复杂度为O(n),空间复杂度为O(n))

  2. 对数组进行排序,然后检查相邻单元格是否相等(时间复杂度为O(nlogn),空间复杂度取决于排序算法是O(1)还是O(n))

stormdrain的解决方案可能是O(n^2),任何涉及扫描数组以搜索重复元素的解决方案也是如此。


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