获取JavaScript数组中的所有唯一值(删除重复项)

2781
我有一个数字数组,我需要确保其中的数字是唯一的。我在互联网上找到了下面的代码片段,它在数组中有零的情况下工作得很好。我在Stack Overflow上找到了一个几乎完全相似的this other script,但它没有出错。
所以为了帮助我学习,有人可以帮我确定原型脚本出了什么问题吗?
Array.prototype.getUnique = function() {
 var o = {}, a = [], i, e;
 for (i = 0; e = this[i]; i++) {o[e] = 1};
 for (e in o) {a.push (e)};
 return a;
}

6
那个较旧的问题是关于查找并返回重复项的(我也感到困惑!)。我的问题更多地涉及到为什么此函数在数组中有零时会失败。 - Mottie
对于未来的读者,当你开始发现你必须要以算法方式频繁地修改数据结构的内容(排序、去重等),或者在每次迭代中搜索其中的元素时,可以安全地假设你一开始使用的数据结构是错误的,并开始使用更适合当前任务的数据结构(例如,在这种情况下,使用哈希集合而不是数组)。 - nurettin
我很久以前从别处复制了这段代码...但它似乎非常简单:o表示对象a表示数组i表示索引,而e则表示嗯,某个东西:P - Mottie
使用 Ramda 中的 R.uniq(list) 可以解决这个问题。https://ramdajs.com/docs/#uniq - varad_s
@user6316468 请注意代码部分的重点。火箭表情符号与示例代码无关。抱歉造成困惑。 - Lukas Liesis
显示剩余9条评论
95个回答

37

我们可以使用ES6集合(set)来完成这个操作:

var duplicatesArray = [1, 2, 3, 4, 5, 1, 1, 1, 2, 3, 4];
var uniqueArray = [...new Set(duplicatesArray)];

console.log(uniqueArray); // [1,2,3,4,5]


36

做这件事最简单、最快(在Chrome浏览器中)的方法如下:

Array.prototype.unique = function() {
    var a = [];
    for (var i=0, l=this.length; i<l; i++)
        if (a.indexOf(this[i]) === -1)
            a.push(this[i]);
    return a;
}

该函数遍历数组中的每个项,测试该项是否已经在列表中,如果不在,则将其推送到要返回的数组中。

根据JSBench的测试,此函数是我能找到的最快的函数 - 当然,您可以添加自己的函数。

非原型版本:

function uniques(arr) {
    var a = [];
    for (var i=0, l=arr.length; i<l; i++)
        if (a.indexOf(arr[i]) === -1 && arr[i] !== '')
            a.push(arr[i]);
    return a;
}

排序

如果需要对数组进行排序,以下是最快的方法:

Array.prototype.sortUnique = function() {
    this.sort();
    var last_i;
    for (var i=0;i<this.length;i++)
        if ((last_i = this.lastIndexOf(this[i])) !== i)
            this.splice(i+1, last_i-i);
    return this;
}

或非原型:

function sortUnique(arr) {
    arr.sort();
    var last_i;
    for (var i=0;i<arr.length;i++)
        if ((last_i = arr.lastIndexOf(arr[i])) !== i)
            arr.splice(i+1, last_i-i);
    return arr;
}

在大多数非Chrome浏览器中,这种方法也比上述方法更快


在Linux上,Chrome 55.0.2883更喜欢您的arr.unique(),而swilliams的arrclone2.sortFilter()最慢(慢78%)。然而,Firefox 51.0.0(带有许多插件)中,swilliams是最快的(但仍比任何其他Chrome结果慢Ops/sec),而mottie的jQuery $.grep(arr,jqFilter)最慢(慢46%)。您的arr.uniq()慢30%。我运行了每个测试两次,并得到了一致的结果。Rafael的arr.getUnique()在两个浏览器中都排名第二。 - Adam Katz
jsPerf目前存在一些问题(详见https://github.com/jsperf/jsperf.com/issues/236),因此我对这个测试的编辑没有完全提交,但是它确实增加了两个测试:Cocco的toUnique()在两个浏览器上都比Vamsi的ES6 list.filter()更快,在FF上击败了swilliams的sortFilter(),速度慢了16%,在Chrome上也比你的排序测试(慢2%)更快,排名第三。 - Adam Katz
啊,我没注意到那些测试案例非常小而且并不重要。接受的答案中的评论描述了这个问题,并提供了一个修正版本来测试,在这个版本中 Rafael 的代码是最快的,而 Joetje50 的 arr.unique 代码则慢了 98%。我还在这条评论中做了另一个修订。 - Adam Katz
7
实际上,您在 unique 函数中实现的算法复杂度为 O(n^2),而在 getUnique 中则为 O(n)。第一个算法在小数据集上可能更快,但数学不会说谎 :) 如果您在由1e5个独特项组成的数组上运行这两个函数,则可以确保后者更快。 - Mikhail Dudin
还被lodash.uniq用于input_array.length < 200,否则使用[...new Set(input_array)]方法。表达为reducer:input_array.reduce((c, v) => {if (!c.includes(v)) c.push(v); return c;}, []) - milahu
显示剩余4条评论

25
["Defects", "Total", "Days", "City", "Defects"].reduce(function(prev, cur) {
  return (prev.indexOf(cur) < 0) ? prev.concat([cur]) : prev;
 }, []);

[0,1,2,0,3,2,1,5].reduce(function(prev, cur) {
  return (prev.indexOf(cur) < 0) ? prev.concat([cur]) : prev;
 }, []);

这个使用对象数组非常有效![].reduce((p, c) => (p.some((item) => c.id === item.id) ? p : p.concat(c)), []) - Alonso Urbano

24

这个问题已经有很多答案了,但并没有解决我的特定需求。

许多答案都是这样的:

a.filter((item, pos, self) => self.indexOf(item) === pos);

但是这不适用于复杂对象的数组。

假设我们有这样一个数组:

const a = [
 { age: 4, name: 'fluffy' },
 { age: 5, name: 'spot' },
 { age: 2, name: 'fluffy' },
 { age: 3, name: 'toby' },
];

如果我们想要带有唯一名称的对象,应该使用array.prototype.findIndex而不是array.prototype.indexOf

a.filter((item, pos, self) => self.findIndex(v => v.name === item.name) === pos);

1
很好的解决方案,注意函数将返回一个新数组(它不会修改自身)。 - Thanwa Ch.
能够很好地处理复杂的对象数组。 - Edgar Quintero
1
只有当元素实际上是完全相同的对象时,@EdgarQuintero才能起作用,因此数组[{a:2},{a:2}]将不起作用,如果您使用indexOf解决方案,但findIndex解决方案可能很有用。 - Dave

23

在查看这里的90+个答案后,我发现还有一个解决方案:

Array.includes有一个非常方便的第二个参数:"fromIndex",因此通过使用它,filter回调方法的每次迭代都会从数组中搜索,从[当前索引]+1开始,这确保不包括当前已过滤的项在查找中并节省时间。

注意-此解决方案不保留顺序,因为它从左到右删除重复的项,但如果数组对象的集合,则胜过Set技巧。

//                               
var list = [0,1,2,2,3,'a','b',4,5,2,'a']

console.log( 
  list.filter((v,i) => !list.includes(v,i+1))
)

// [0,1,3,"b",4,5,2,"a"]

说明:

例如,假设filter函数当前正在迭代索引2的值恰好为2。然后扫描数组中包含重复项(使用includes方法)的那一部分是从索引i+1开始的所有元素:

                               
[0, 1, 2,   2 ,3 ,'a', 'b', 4, 5, 2, 'a']
          |---------------------------|

由于当前被过滤项的值2包含在数组的其余部分中,因此它将被过滤掉,因为有一个感叹号标记,它否定了过滤规则。


如果顺序很重要,请使用此方法:

//                               
var list = [0,1,2,2,3,'a','b',4,5,2,'a']

console.log( 
  // Initialize with empty array and fill with non-duplicates
  list.reduce((acc, v) => (!acc.includes(v) && acc.push(v), acc), [])
)

// [0,1,2,3,"a","b",4,5]


不幸的是,这将保留每个值的最后一个实例,而不是第一个实例。(这可能没问题,但我认为通常期望保留第一个实例) - lapo
尽管这是一个很好的优化想法,但我认为人们不会想使用它,因为它会保留最后一项,基本上会搞乱数组的顺序。 - minhtus

22
可以使用内置函数Array.prototype.filter()Array.prototype.indexOf()来实现。 array.filter((x, y) => array.indexOf(x) == y)
var arr = [1, 2, 3, 3, 4, 5, 5, 5, 6, 7, 8, 9, 6, 9];

var newarr = arr.filter((x, y) => arr.indexOf(x) == y);

console.log(newarr);


21

这个原型 getUnique 并不完全正确,因为如果我有一个数组,比如["1",1,2,3,4,1,"foo"]它会返回["1","2","3","4"],但是"1"是字符串而1是整数,它们是不同的。

以下是正确的解决方案:

Array.prototype.unique = function(a){
    return function(){ return this.filter(a) }
}(function(a,b,c){ return c.indexOf(a,b+1) < 0 });

使用:

var foo;
foo = ["1",1,2,3,4,1,"foo"];
foo.unique();
以上代码将生成["1",2,3,4,1,"foo"]

3
请注意,$foo = 'bar' 是 PHP 声明变量的方式。虽然它在 JavaScript 中也可以工作,但会创建一个隐式的全局变量,通常不应该这样做。 - Camilo Martin
1
@CamiloMartin 对不起,但你错了,$foo是全局的,因为这个例子不在闭包中,并且缺少var关键字。与美元符号无关http://jsfiddle.net/robaldred/L2MRb/ - Rob
10
@Rob,这正是我想说的,PHP程序员会认为$foo是在JavaScript中声明变量的方式,而实际上应该用var foo - Camilo Martin

19

原始值

使用Set(推荐)

var array = ["FreePhoenix888", "FreePhoenix888", "konard", "FreePhoenix888"];

let set = [...new Set(array)];

console.log(set); // ["FreePhoenix888", "konard"]

没有Set

function filterUniqueObjects(value, index, array) {
  return array.indexOf(value) === index;
}

// usage example:
var array = ["FreePhoenix888", "FreePhoenix888", "konard", "FreePhoenix888"];
var arrayOfUniqueItems = array.filter(filterUniqueObjects);

console.log(arrayOfUniqueItems); // ["FreePhoenix888", "konard"]

对象

此示例演示了如何过滤不仅是原始值数组,而是对象数组。我已添加注释,以便根据您的要求更轻松地理解可以更改哪些内容。

let array = [
  { name: '@deep-foundation/core', version: '0.0.2' },
  { name: '@deep-foundation/capacitor-device', version: '10.0.1' },
  { name: '@deep-foundation/capacitor-device', version: '10.0.2' },
];

// Of course you can inline this function as filter argument uniqueArray.filter((item, index, self) => self.findIndex(innerItem => innerItem.name === item.name) === index);
function filterUniqueObjects(value, index, self) {
  return (
    self.findIndex(
      // Modify this function as you desire. You may want to calculate uniqueness depending only on specific fields, not all
      (obj) => obj.name === value.name
    ) === index
  );
};

let uniqueArray = array
  .reverse() // If you want latest duplicates to remain
  .filter(filterUniqueObjects)
  .reverse(); // To get back to original order after first reverse

console.log(uniqueArray)


18
[...new Set(duplicates)]

这是最简单的方法,参考自 MDN Web Docs

const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)]) // [2, 3, 4, 5, 6, 7, 32]

1
虽然这段代码可以解决问题,但是包含一个解释如何和为什么解决问题将会真正有助于提高您的帖子质量,并可能导致更多的赞。请记住,您正在为未来的读者回答问题,而不仅仅是现在提问的人。请编辑您的答案以添加解释,并指示适用的限制和假设。 - id.ot
1
与一年前的先前答案完全相同。 - vsync

17

不需要扩展Array.prototype(据说这是一种不好的做法),也不使用jquery/underscore,你可以简单地使用filter过滤数组。

只保留最后一个出现的:

    function arrayLastUnique(array) {
        return array.filter(function (a, b, c) {
            // keeps last occurrence
            return c.indexOf(a, b + 1) < 0;
        });
    },

或者第一次出现:

    function arrayFirstUnique(array) {
        return array.filter(function (a, b, c) {
            // keeps first occurrence
            return c.indexOf(a) === b;
        });
    },

好的,这只是 Javascript ECMAScript 5+,也就是说只支持 IE9+,但对于原生 HTML/JS 的开发非常有用(Windows Store App,Firefox OS,Sencha,Phonegap,Titanium等)。


2
它是js 1.6并不意味着你不能使用filter。在MDN页面上,他们为Internet Explorer提供了一个实现,也就是说,旧版浏览器。另外:JS 1.6仅指Firefox的js引擎,但正确的说法是它是ECMAScript 5。 - Camilo Martin

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