在Javascript中,我如何检查一个数组是否有重复的值?

157

可能是重复问题:
查找JavaScript数组中重复值的最简单方法

如何检查一个数组是否有重复值?

如果数组中有相同元素,则返回true。否则,返回false。

['hello','goodbye','hey'] //return false because no duplicates exist
['hello','goodbye','hello'] // return true because duplicates exist

请注意,我不关心查找重复项,只想知道数组是否包含重复项的布尔结果。


这里是:https://dev59.com/eXRA5IYBdhLWcg3wvgsb#840808 - Ofer Zelig
2
我不想要一个去重后的列表。我只想知道一个列表中是否有重复项,返回真或假。 - user847495
9
这个问题不是重复的。因为 @user847495 只是想检查是否存在重复项,所以解决方案比查找所有重复项所需的时间/步骤更快/更容易。例如,你可以使用这个链接:http://codr.io/v/bvzxhqm - alden
2
使用 underscore ,简单的技巧 var test=['hello','goodbye','hello'] ; if ( test.length != _.unique(test).length ) { // some code } - Sai Ram
4
不是标记问题的重复。请在标记前注意一下。 - John Weisz
显示剩余3条评论
12个回答

329

如果你的环境支持ES2015(截至本文撰写时,包括io.js、IE11、Chrome、Firefox、WebKit nightly),则以下内容将可行并且速度快(即O(n)):

function hasDuplicates(array) {
    return (new Set(array)).size !== array.length;
}

如果你只需要数组中的字符串值,以下方法可以使用:

function hasDuplicates(array) {
    var valuesSoFar = Object.create(null);
    for (var i = 0; i < array.length; ++i) {
        var value = array[i];
        if (value in valuesSoFar) {
            return true;
        }
        valuesSoFar[value] = true;
    }
    return false;
}
我们使用一个名为“valuesSoFar”的哈希表,它的键是我们到目前为止在数组中看到的值。我们使用“in”进行查找,以查看是否已经发现该值;如果是,则我们退出循环并返回“true”。
如果您需要一个适用于不仅仅是字符串值的函数,以下代码可以工作,但不够高效,其时间复杂度为O(n^2),而不是O(n)。
function hasDuplicates(array) {
    var valuesSoFar = [];
    for (var i = 0; i < array.length; ++i) {
        var value = array[i];
        if (valuesSoFar.indexOf(value) !== -1) {
            return true;
        }
        valuesSoFar.push(value);
    }
    return false;
}

不同之处仅在于我们使用一个数组而不是哈希表来存储valuesSoFar,因为JavaScript的“哈希表”(即对象)只有字符串键。这意味着我们失去了in的O(1)查找时间,而得到了indexOf的O(n)查找时间。


3
关于您提供的第一个例子,验证方式难道不恰好相反吗?如果您的函数命名为 hasDuplicates,那么它应该检查在将集合转换过程中其大小是否实际上缩小了。因此,布尔运算符应该是 !== 而不是 === - Tim Daubenschütz
请编辑。我不能编辑,因为我不会修改超过6个字符。 - Tim Daubenschütz
1
根据MDN的说明,IE11不支持第一个示例中使用的构造函数。 - adam77
1
普通的JS版本对于以下数组返回true[1,'1'] - Kunal
1
嗨!我想贡献另一个解决方案,最终我使用了它。以防对某人有用:[1,2,3,4,4].filter( (v, i, arr) => i !== arr.indexOf(v) )。它将返回一个包含重复值的数组(除第一次出现的值外)。 - Davo
显示剩余4条评论

18

你可以使用 SET 来去除重复项并进行比较,如果将数组复制到一个 SET 中,它将删除任何重复的元素。然后只需比较数组的长度和 SET 的大小即可。

function hasDuplicates(a) {

  const noDups = new Set(a);

  return a.length !== noDups.size;
}

17

使用ES6的一行解决方案

const arr1 = ['hello','goodbye','hey'] 
const arr2 = ['hello','goodbye','hello'] 

const hasDuplicates = (arr) => arr.length !== new Set(arr).size;
console.log(hasDuplicates(arr1)) //return false because no duplicates exist
console.log(hasDuplicates(arr2)) //return true because duplicates exist

const s1 = ['hello','goodbye','hey'].some((e, i, arr) => arr.indexOf(e) !== i)
const s2 = ['hello','goodbye','hello'].some((e, i, arr) => arr.indexOf(e) !== i);

console.log(s1) //return false because no duplicates exist
console.log(s2) //return true because duplicates exist


3
FYI - Set 有一个 .size 属性,所以您不必将其展开为数组来获取 .length - KyleMit
1
谢谢,我已经改过了。 - Kordrad

6

另一种方法(也适用于数组内的对象/数组元素1)可能是这样的2

function chkDuplicates(arr,justCheck){
  var len = arr.length, tmp = {}, arrtmp = arr.slice(), dupes = [];
  arrtmp.sort();
  while(len--){
   var val = arrtmp[len];
   if (/nul|nan|infini/i.test(String(val))){
     val = String(val);
    }
    if (tmp[JSON.stringify(val)]){
       if (justCheck) {return true;}
       dupes.push(val);
    }
    tmp[JSON.stringify(val)] = true;
  }
  return justCheck ? false : dupes.length ? dupes : null;
}
//usages
chkDuplicates([1,2,3,4,5],true);                           //=> false
chkDuplicates([1,2,3,4,5,9,10,5,1,2],true);                //=> true
chkDuplicates([{a:1,b:2},1,2,3,4,{a:1,b:2},[1,2,3]],true); //=> true
chkDuplicates([null,1,2,3,4,{a:1,b:2},NaN],true);          //=> false
chkDuplicates([1,2,3,4,5,1,2]);                            //=> [1,2]
chkDuplicates([1,2,3,4,5]);                                //=> null

另请参阅...

1需要支持JSON的浏览器,如果不支持则需要JSON库
2 编辑: 现在该函数可以用于简单检查或返回重复值数组。


3
值得注意的非致命问题:1)会改变原始数组的排序;2)不区分nullNaNInfinity+Infinity-Infinity;3)如果两个对象具有相同的自有属性,则被认为相等,即使它们具有不同的原型。 - Domenic
1
@Domenic:是的,应该提到它。编辑以避免原始数组的变异。 - KooiInc
@Domenic:已更正为null/NaN/[+/-]Infinity,请查看编辑。 - KooiInc
@Domenic:问题3)对我来说实际上不是问题,因为这正是我想要的。我不关心原型,只关心值。 - awe

4

你可以利用indexOflastIndexOf方法。如果两个索引不相同,那么你就有重复的内容。

function containsDuplicates(a) {
  for (let i = 0; i < a.length; i++) {
    if (a.indexOf(a[i]) !== a.lastIndexOf(a[i])) {
      return true
    }
  }
  return false
}

4

如果你处理的是简单值,可以使用array.some()indexOf()

例如,假设vals["b", "a", "a", "c"]

const allUnique = !vals.some((v, i) => vals.indexOf(v) < i);

some()会在任何一个表达式返回true时返回true。这里我们将会遍历值(从索引0开始),并调用indexOf(),它将返回给定项的第一次出现的索引(如果不在数组中则返回-1)。如果它的id比当前的小,那么它之前必须至少有一个相同的值。因此,迭代3将返回true,因为"a"(在索引2处)首次出现在索引1处。


2

这很简单,你可以使用Array.prototype.every函数。

function isUnique(arr) {
  const isAllUniqueItems = input.every((value, index, arr) => {
    return arr.indexOf(value) === index; //check if any duplicate value is in other index
  });

  return isAllUniqueItems;
}

2

为什么使用这种方法:

我认为在处理多个数组和循环时,这是最好的方法。这个例子非常简单,但在某些情况下,比如在多个循环和对象迭代中,这是最可靠和最优化的方法。

解释:

在这个例子中,数组被迭代,元素与array[i]相同,其中i是循环当前所在的数组位置。然后函数检查读取数组中的位置,该位置被初始化为空。如果元素不在读取数组中,则返回-1并将其推入读取数组;否则返回其位置并且不会被推入。一旦所有数组元素都被迭代完毕,读取数组将被打印到控制台。

let array = [1, 2, 3, 4, 5, 1, 2, 3, 5]
let read = []

array.forEach(element => {
  if (read.indexOf(element) == -1) {
    read.push(element)
    console.log("This is the first time" + element + " appears in the array")
  } else {
    console.log(element + " is already in the array")
  }
})

console.log(read)


1
使用Set解决方案的一个好处是,在查找列表中现有项目时具有O(1)的性能,而不必回到它上面循环。
使用Some解决方案的一个好处是,在早期发现重复项时进行短路,因此当条件已满足时,您无需继续评估数组的其余部分。
将两者结合起来的一种解决方案是逐步构建集合,如果当前元素存在于集合中,则提前终止,否则添加它并继续下一个元素。

const hasDuplicates = (arr) => {
  let set = new Set()
  return arr.some(el => {
    if (set.has(el)) return true
    set.add(el)
  })
}

hasDuplicates(["a","b","b"]) // true
hasDuplicates(["a","b","c"]) // false

根据JSBench.me的数据,这个实现在各种使用情况下都表现良好。如果没有重复项,则采用集合大小方法最快,如果有早期重复项,则采用检查一些+indexOf方法最快,但是该解决方案在两种情况下都表现良好,是一个全面的好实现。

0
    this.selectedExam = [];
    // example exam obj: {examId:1, name:'ExamName'}
    onExamSelect(exam: any) {
    if(!this.selectedExam.includes(exam?.name)){ 
      this.selectedExam.push(exam?.name);
    }}

在上面的代码中,我使用了一个数组,并在触发特定函数(onExamSelect)时检查重复项并推送唯一元素。

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