我有一个非常简单的JavaScript数组,可能包含重复项。
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
我需要删除重复项并将唯一值放入一个新数组。
我可以指出我尝试过的所有代码,但我认为这没有用,因为它们不起作用。我接受jQuery解决方案。
我有一个非常简单的JavaScript数组,可能包含重复项。
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
我需要删除重复项并将唯一值放入一个新数组。
我可以指出我尝试过的所有代码,但我认为这没有用,因为它们不起作用。我接受jQuery解决方案。
uniq = [...new Set(array)];
(请注意,变量uniq
将是一个数组......new Set()
将其转换为Set,但[...]将其转换回数组)
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
基本上,我们遍历数组,并对每个元素检查其在数组中的第一个位置是否等于当前位置。显然,对于重复元素,这两个位置是不同的。
使用过滤器回调的第三个(“this array”)参数,可以避免对数组变量进行闭包:
uniqueArray = a.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
})
虽然这个算法简洁,但对于大型数组来说效率并不高(二次时间复杂度)。
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
通常做法是将每个元素放入散列表中,然后立即检查其是否存在。这样可以使时间复杂度线性,但至少有两个缺点:
uniq([1,“1”])
将仅返回[1]
uniq([{foo:1},{foo:2}])
将仅返回[{foo:1}]
.也就是说,如果您的数组仅包含基元,并且您不关心类型(例如,它总是数字),则此解决方案是最佳的。
一种通用解决方案结合了两种方法:对于基元,它使用哈希查找;对于对象,则使用线性搜索。
function uniq(a) {
var prims = {"boolean":{}, "number":{}, "string":{}}, objs = [];
return a.filter(function(item) {
var type = typeof item;
if(type in prims)
return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
else
return objs.indexOf(item) >= 0 ? false : objs.push(item);
});
}
另一个选项是先对数组进行排序,然后移除每个与前面一个相等的元素:
function uniq(a) {
return a.sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
}
注意,这种方法无法用于对象(因为所有对象对于sort
来说都是相等的)。此外,我们会在原始数组上产生副作用,这不好!但是,如果您的输入已经排序,那么这就是一种可行的方法(只需从上面删除sort
)。
有时候希望根据某些标准唯一化列表,而不仅仅是相等,例如过滤出不同但共享某些属性的对象。这可以通过传递回调来优雅地完成。每个元素都会应用这个“键”回调函数,并且具有相等“键”的元素将被删除。由于key
期望返回一个原始数据类型,因此哈希表在这里很好用:
function uniqBy(a, key) {
var seen = {};
return a.filter(function(item) {
var k = key(item);
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
})
}
一个特别有用的 key()
是 JSON.stringify
,它可以移除物理上不同但“看起来”相同的对象:
a = [[1,2,3], [4,5,6], [1,2,3]]
b = uniqBy(a, JSON.stringify)
console.log(b) // [[1,2,3], [4,5,6]]
如果key
不是基本数据类型,你就必须使用线性搜索:
function uniqBy(a, key) {
var index = [];
return a.filter(function (item) {
var k = key(item);
return index.indexOf(k) >= 0 ? false : index.push(k);
});
}
在ES6中,您可以使用Set
:
function uniqBy(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
或者一个Map
:
function uniqBy(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
这两种方法都可以使用非原始键进行操作。
当通过键来移除对象时,可能需要保留“相等”对象的第一个或最后一个。
使用上面提到的 Set
可以保留第一个,而使用 Map
则可以保留最后一个:
function uniqByKeepFirst(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
function uniqByKeepLast(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
//
data = [
{a:1, u:1},
{a:2, u:2},
{a:3, u:3},
{a:4, u:1},
{a:5, u:2},
{a:6, u:3},
];
console.log(uniqByKeepFirst(data, it => it.u))
console.log(uniqByKeepLast(data, it => it.u))
无论是Underscore还是Lo-Dash都提供了uniq
方法,它们的算法基本上与上面的第一个代码片段相似,归结为以下内容:
var result = [];
a.forEach(function(item) {
if(result.indexOf(item) < 0) {
result.push(item);
}
});
这是二次的,但有很多其他好处,例如包装本机的indexOf
,可以通过键(在他们的术语中为iteratee
)进行唯一化,并对已排序的数组进行优化。
如果您正在使用jQuery并且无法忍受任何没有美元符号的东西,则可以这样使用:
$.uniqArray = function(a) {
return $.grep(a, function(item, pos) {
return $.inArray(item, a) === pos;
});
}
这句话是第一个片段的变体。
在JavaScript中,函数调用的代价比较高,因此上述解决方案虽然简洁,但不是特别高效。为了获得最佳性能,请使用循环替换filter
并消除其他函数调用:
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
这段丑陋的代码与上面第三个片段执行相同的操作,但快了一个数量级(截至2017年仅快了两倍- JS核心团队做得很好!)
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
/////
var r = [0,1,2,3,4,5,6,7,8,9],
a = [],
LEN = 1000,
LOOPS = 1000;
while(LEN--)
a = a.concat(r);
var d = new Date();
for(var i = 0; i < LOOPS; i++)
uniq(a);
document.write('<br>uniq, ms/loop: ' + (new Date() - d)/LOOPS)
var d = new Date();
for(var i = 0; i < LOOPS; i++)
uniq_fast(a);
document.write('<br>uniq_fast, ms/loop: ' + (new Date() - d)/LOOPS)
ES6提供了Set对象,它使事情变得更加容易:
function uniq(a) {
return Array.from(new Set(a));
}
let uniq = a => [...new Set(a)];
uniq
版本:function* uniqIter(a) {
let seen = new Set();
for (let x of a) {
if (!seen.has(x)) {
seen.add(x);
yield x;
}
}
}
// example:
function* randomsBelow(limit) {
while (1)
yield Math.floor(Math.random() * limit);
}
// note that randomsBelow is endless
count = 20;
limit = 30;
for (let r of uniqIter(randomsBelow(limit))) {
console.log(r);
if (--count === 0)
break
}
// exercise for the reader: what happens if we set `limit` less than `count` and why
O(n^2)
的解法,在大数组中可能会运行非常缓慢... - seriyPS["toString", "valueOf", "failed"]
。完全删除 toString
和 valueOf
。使用 Object.create(null)
替代 {}
。 - Charles Beattie使用jQuery快速而简单的方法:
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniqueNames = [];
$.each(names, function(i, el){
if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el);
});
inArray
解决方案,因此我要再次提醒:这个解决方案的时间复杂度为O(n^2),效率低下。 - Casey Kuball看够了使用for循环或jQuery的所有不良示例。现在,Javascript拥有完美的工具:sort、map和reduce。
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniq = names.reduce(function(a,b){
if (a.indexOf(b) < 0 ) a.push(b);
return a;
},[]);
console.log(uniq, names) // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]
// one liner
return names.reduce(function(a,b){if(a.indexOf(b)<0)a.push(b);return a;},[]);
可能有更快的方法,但这个方法相当不错。
var uniq = names.slice() // slice makes copy of array before sorting it
.sort(function(a,b){
return a > b;
})
.reduce(function(a,b){
if (a.slice(-1)[0] !== b) a.push(b); // slice(-1)[0] means last item in array without removing it (like .pop())
return a;
},[]); // this empty array becomes the starting value for a
// one liner
return names.slice().sort(function(a,b){return a > b}).reduce(function(a,b){if (a.slice(-1)[0] !== b) a.push(b);return a;},[]);
在ES6中,您可以使用Set和Spread轻松高效地删除所有重复项:
var uniq = [ ...new Set(names) ]; // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]
有人询问如何根据唯一名称的数量对结果进行排序:
var names = ['Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Nancy', 'Carl']
var uniq = names
.map((name) => {
return {count: 1, name: name}
})
.reduce((a, b) => {
a[b.name] = (a[b.name] || 0) + b.count
return a
}, {})
var sorted = Object.keys(uniq).sort((a, b) => uniq[a] < uniq[b])
console.log(sorted)
const uniqueList = [ ...(new Set(names)) ];
(带有额外的括号)。 - Alexander Mills使用对象像 Set 一样删除重复项的原生 JS 方法
你可以尝试将数组放入一个对象中,然后遍历它的键来实现去除重复项:
function remove_duplicates(arr) {
var obj = {};
var ret_arr = [];
for (var i = 0; i < arr.length; i++) {
obj[arr[i]] = true;
}
for (var key in obj) {
ret_arr.push(key);
}
return ret_arr;
}
使用Vanilla JS:通过跟踪已经看到的值来删除重复项(保持原有顺序)
或者,为了保持顺序,可以使用一个对象来存储所有先前看到的值,并在添加到数组之前检查这些值是否存在。
function remove_duplicates_safe(arr) {
var seen = {};
var ret_arr = [];
for (var i = 0; i < arr.length; i++) {
if (!(arr[i] in seen)) {
ret_arr.push(arr[i]);
seen[arr[i]] = true;
}
}
return ret_arr;
}
ECMAScript 6:使用新的Set数据结构(有序)
ECMAScript 6增加了新的Set
数据结构,使您能够存储任何类型的值。 Set.values
以插入顺序返回元素。
function remove_duplicates_es6(arr) {
let s = new Set(arr);
let it = s.values();
return Array.from(it);
}
使用示例:
a = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
b = remove_duplicates(a);
// b:
// ["Adam", "Carl", "Jenny", "Matt", "Mike", "Nancy"]
c = remove_duplicates_safe(a);
// c:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]
d = remove_duplicates_es6(a);
// d:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]
var c = Object.keys(b)
。需要注意的是,这种方法只适用于字符串,但没关系,因为这也是最初的问题所要求的。 - amenthesobj[arr[i]] = true;
发生了什么? - kittui
个元素,并将其放入对象(用作集合)中。键是元素,值是true
,这完全是任意的,因为我们只关心对象的键。 - Casey Kuball使用数组 .filter
和 .indexOf
函数的单行版本:
arr = arr.filter(function (value, index, array) {
return array.indexOf(value) === index;
});
这是一个库,其中包含许多操作数组的函数。
它是与jQuery的礼服和Backbone.js的吊带相匹配的领带。
_.uniq(array, [isSorted], [iterator])
别名:unique
使用 === 测试对象是否相等,生成array的无重复版本。如果您事先知道array已排序,则传递true将运行更快的算法。如果您想基于转换计算唯一项,请传递一个iterator函数。
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
alert(_.uniq(names, false));
注意:Lo-Dash(一个Underscore的竞争对手)也提供了一个类似的.uniq实现。
[...new Set(Array)]
已经足够了,伙计。 - norbekoff一行:
let names = ['Mike','Matt','Nancy','Adam','Jenny','Nancy','Carl', 'Nancy'];
let dup = [...new Set(names)];
console.log(dup);
你可以在JavaScript中简单地完成它,利用filter
方法的第二个索引参数进行操作:
你只需使用JavaScript中的filter
方法,并利用其提供的第二个索引参数即可轻松实现此功能:
var a = [2,3,4,5,5,4];
a.filter(function(value, index){ return a.indexOf(value) == index });
或者简写为
a.filter((v,i) => a.indexOf(v) == i)
a.indexOf(v) == i
应该改为 a.indexOf(v) === a.lastIndexOf(v)
。 - Hitmandsa
变量也可以工作,因为数组是 filter
的第三个参数:[1/0, 2,1/0,2,3].filter((v,i,a) => a.indexOf(v) === i)
(请注意,它也可以与 Infinity
很好地配合使用☺) - Xenos.filter((v,i, array) => array.indexOf(v) == i)
。 - omeanwell可以像这样使用
Array.filter()
var actualArr = ['Apple', 'Apple', 'Banana', 'Mango', 'Strawberry', 'Banana'];
console.log('Actual Array: ' + actualArr);
var filteredArr = actualArr.filter(function(item, index) {
if (actualArr.indexOf(item) == index)
return item;
});
console.log('Filtered Array: ' + filteredArr);
这可以在 ES6 中缩短为
actualArr.filter((item,index,self) => self.indexOf(item)==index);
这里有一个关于Array.filter()
的简洁解释。
使用本地JavaScript函数最简洁的方法来从数组中删除重复项是使用以下类似的序列:
vals.sort().reduce(function(a, b){ if (b != a[0]) a.unshift(b); return a }, [])
在reduce函数中没有必要使用slice或indexOf,就像我在其他示例中看到的那样!但与filter函数一起使用是有意义的:
不需要在reduce函数中使用slice或indexOf,这是其他示例中出现过的错误方法。然而,将它们与filter函数结合使用则是合理的:
vals.filter(function(v, i, a){ return i == a.indexOf(v) })
另一种ES6(2015)的方法是在一些浏览器上已经可以使用:
Array.from(new Set(vals))
甚至可以使用扩展运算符:
[...new Set(vals)]
干杯!
set
机制的简单一行代码。这是为了一个自定义的自动化任务,所以我并不担心在最新版本的 Chrome 中使用它(在 jsfiddle 中)。然而,我仍然想知道最短的 所有浏览器兼容 的去重数组的方法。 - Alexander Dixon.reduce()
不是跨浏览器兼容的,因为我需要应用一个 poly-fill。不过还是感谢您的回复。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce - Alexander Dixon
_.uniq(peoplenames)
解决了这个问题。请参考 http://lodash.com/docs#uniq 了解更多信息。 - Connor Leechconst result = data.sort().filter((v, idx, t) => idx==0 || v != t[idx-1]);
- Didier68