问:
这个问题在这里已经有了答案:Get all unique values in a JavaScript array (remove duplicates) (85 answers) 4 年前关闭。
我有一个非常简单的 JavaScript 数组,它可能包含也可能不包含重复项。
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
我需要删除重复项并将唯一值放入一个新数组中。
我可以指出我尝试过的所有代码,但我认为它没有用,因为它们不起作用。我也接受 jQuery 解决方案。
类似的问题:
获取数组中的所有非唯一值(即:重复/多次出现)
答1:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
TL;博士
使用 Set 构造函数和 spread syntax:
uniq = [...new Set(array)];
“聪明”但幼稚的方式
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
基本上,我们遍历数组,并且对于每个元素,检查该元素在数组中的第一个位置是否等于当前位置。显然,这两个位置对于重复元素是不同的。
使用过滤器回调的第三个(“这个数组”)参数,我们可以避免数组变量的关闭:
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);
});
}
这就是通常的做法。这个想法是将每个元素放在一个哈希表中,然后立即检查它的存在。这给了我们线性时间,但至少有两个缺点:
由于哈希键在 JavaScript 中只能是字符串或符号,因此此代码不区分数字和“数字字符串”。也就是说, 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;
}
这段丑陋的代码与上面的代码片段 #3 相同,但速度快了一个数量级(截至 2017 年,它的速度只有两倍 - JS 核心人员做得很好!)
函数 uniq(a) { var seen = {}; return a.filter(function(item) { return seen.hasOwnProperty(item) ? false : (seen[item] = true); }); } function uniq_fast(a) { var seen = {};变种 = []; var len = a.length;变量 j = 0; for(var i = 0; i < len; i++) { var item = a[i];如果(看到[项目]!== 1){看到[项目] = 1;出[j++] = 项目; } } 返回; } / var r = [0,1,2,3,4,5,6,7,8,9], a = [], LEN = 1000, LOOPS = 1000; while(LEN–) a = a.concat®; var d = 新日期(); for(var i = 0; i < LOOPS; i++) uniq(a); document.write('uniq, ms/loop: ’ + (new Date() - d)/LOOPS) var d = new Date(); for(var i = 0; i < LOOPS; i++) uniq_fast(a); document.write('uniq_fast, ms/loop: ’ + (new Date() - d)/LOOPS)
ES6
ES6 提供了 Set 对象,这让事情变得简单多了:
function uniq(a) {
return Array.from(new Set(a));
}
或者
let uniq = a => [...new Set(a)];
请注意,与 python 不同,ES6 集合是按插入顺序迭代的,因此此代码保留了原始数组的顺序。
但是,如果您需要一个具有唯一元素的数组,为什么不从一开始就使用集合呢?
发电机
uniq 的“惰性”、基于生成器的版本可以在相同的基础上构建:
从参数中获取下一个值
如果已经看过,请跳过它
否则,产生它并将其添加到一组已经看到的值
函数* uniqIter(a) { 让我们看到 = new Set(); for (let x of a) { if (!seen.has(x)) { seen.add(x);产量 x; } } } // 示例:function* randomsBelow(limit) { while (1) yield Math.floor(Math.random() * limit); } // 注意 randomsBelow 是无穷的 count = 20;限制 = 30; for (let r of uniqIter(randomsBelow(limit))) { console.log®; if (–count === 0) break } // 读者练习:如果我们将 limit
设置为小于 count
会发生什么以及为什么
在 ECMAScript 5 中引入了 filter 和 indexOf,因此这在旧 IE 版本 (<9) 中不起作用。如果您关心这些浏览器,您将不得不使用具有类似功能的库(jQuery、underscore.js 等)
@RoderickObrist 如果您希望您的页面在旧版浏览器中工作,您可能会这样做
这是 O(n^2) 解决方案,它可以在大型阵列中运行非常缓慢...
试试这个数组:["toString", "valueOf", "failed"]。 toString 和 valueOf 被完全剥离。使用 Object.create(null) 而不是 {}。
与其他解决方案相比,任何人都知道 Set 转换解决方案有多快?
答2:
huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式
使用 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);
});
不介意那些不使用它的人的非 jquery 答案
由于这已由知名人士恢复为原始 inArray 解决方案,因此我将再次提及:此解决方案是 O(n^2),因此效率低下。
我真的希望在 2020 年我们可以开始贬值 jQuery 和其他更过时的答案...... Stackoverflow 开始在这里显示一些年龄......
我同意@NickSteele,但如果您查看选票而不是接受的答案,我发现它确实会随着时间的推移自然发生。随着旧的弃用答案被否决,最佳答案将被排在首位
让 uniqueNames = names.filter((item, pos ,self) => self.indexOf(item) == pos);
答3:
huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。
厌倦了使用 for 循环或 jQuery 看到所有不好的示例。如今,Javascript 拥有完美的工具:排序、映射和归约。
Uniq 在保持现有订单的同时减少
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;},[]);
排序更快的 uniq
可能有更快的方法,但这个方法相当不错。
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;},[]);
2015 年更新:ES6 版本:
在 ES6 中,你有 Sets 和 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)
好的!是否可以根据重复对象的频率对数组进行排序?那么上例中的 "Nancy" 是否移动到修改后的数组的前面(或后面)?
@ALx - 我更新了一个基于出现排序的示例。
在您的第二个示例中,sort() 似乎被错误地调用:如果 a 是 < b 然后它返回与 a == b 相同的值,这可能导致未排序的结果。除非你在这里做一些我错过的聪明的事情,否则应该是.sort(function(a,b){ return a > b ? 1 : a < b ? -1 : 0; })
如果数据只是一个名称数组,除了消除重复之外没有其他要求,为什么还要使用排序、映射和归约呢?只需使用一组在 O(n) 时间内完成的工作。 -- msdn.microsoft.com/en-us/library/dn251547
@Dave 是的 - 请参阅上面 [...new Set(names)] 中的示例
答4:
huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。
Vanilla 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)。应该注意的是,这种方法仅适用于字符串,但没关系,这就是原始问题所要求的。
还应注意,您可能会丢失数组的顺序,因为对象不会按顺序保持其属性。
@JuanMendes 我创建了一个订单安全版本,如果之前没有看到该值,它只会复制到新数组。
这条线 obj[arr[i]] = true; 发生了什么?
@kittu,即获取数组的第 i 个元素,并将其放入对象中(用作集合)。键是元素,值是 true,这完全是任意的,因为我们只关心对象的键。
答5:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
使用数组 .filter 和 .indexOf 函数的单行版本:
arr = arr.filter(function (value, index, array) {
return array.indexOf(value) === index;
});
愿意解释一下它是如何消除欺骗的吗?
@web_dev:它没有!我已经更正了之前破坏代码的编辑。希望它现在更有意义。谢谢提问!
不幸的是,如果这是一个大数组,则性能很差—— arr.indexOf 为 O(n),这使得该算法为 O(n^2)
正如@CaseyKuball 建议的那样,这个解决方案实际上非常慢 - 请参阅stackoverflow.com/questions/67424599/…
答6:
huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。
使用 Underscore.js
它是一个包含大量用于操作数组的函数的库。
这是与 jQuery 的 tux 和 Backbone.js 的吊带搭配的领带。
_.uniq
_.uniq(array, [isSorted], [iterator]) 别名:unique 生成数组的无重复版本,使用 === 来测试对象是否相等。如果您事先知道数组已排序,则为 isSorted 传递 true 将运行更快的算法。如果要基于转换计算唯一项,请传递迭代器函数。
Example
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
alert(_.uniq(names, false));
注意:Lo-Dash(underscore 的竞争对手)也提供了类似的 .uniq 实现。
不幸的是,下划线不提供定义自定义相等函数的能力。他们确实允许的回调是针对“迭代”函数,例如带有 args(项目、值、数组)的函数。
[...new Set(Array)] 绰绰有余
@norbekoff - 绝对,大声笑。 〜10年后!
答7:
与HuntsBot一起,探索全球自由职业机会–huntsbot.com
一条线:
let names = ['Mike','Matt','Nancy','Adam','Jenny','Nancy','Carl', 'Nancy'];
let dup = [...new Set(names)];
console.log(dup);
最佳答案,如果您使用的是 ES6
这3个点是什么意思?
@Vitalicus,这是 ES6 中的扩展运算符。阅读更多here
答8:
huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求
借助 filter 方法的第二个 - 索引 - 参数,您可以简单地在 JavaScript 中执行此操作:
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)
@Hitmands 你从右边比较,我从左边比较。没有其他的 。
也可以在不需要 a 变量的情况下工作,因为数组是 filter 的第三个参数:[1/0, 2,1/0,2,3].filter((v,i,a) => a.indexOf(v) === i)(请注意,它也适用于 Infinity ☺ )
答9:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
像这样使用 Array.filter()
var actualArr = [‘Apple’, ‘Apple’, ‘Banana’, ‘Mango’, ‘Strawberry’, ‘Banana’]; console.log(‘实际数组:’ + actualArr); var filteredArr = actualArr.filter(function(item, index) { if (actualArr.indexOf(item) == index) return item; }); console.log(‘过滤后的数组:’+filteredArr);
这可以在 ES6 中缩短到
actualArr.filter((item,index,self) => self.indexOf(item)==index);
Here 很好地解释了 Array.filter()
你能详细说明你在这里做了什么吗? :-)
当数组是数组数组时不起作用
不适用于区分大小写的数组
答10:
huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。
使用原生 javascript 函数从数组中删除重复项的最简洁方法是使用如下序列:
vals.sort().reduce(function(a, b){ if (b != a[0]) a.unshift(b); return a }, [])
在 reduce 函数中不需要 slice 或 indexOf,就像我在其他示例中看到的那样!将它与过滤器功能一起使用是有意义的:
vals.filter(function(v, i, a){ return i == a.indexOf(v) })
已经在一些浏览器上运行的另一种 ES6(2015) 方法是:
Array.from(new Set(vals))
甚至使用 spread operator:
[...new Set(vals)]
干杯!
Set 对于那些习惯使用 python 的人来说非常棒并且非常直观。太糟糕了,他们没有那些伟大的(联合,相交,差异)方法。
我使用了利用 set 机制的简单的一行代码。这是针对自定义自动化任务的,因此我对在最新版本的 Chrome(在 jsfiddle 内)中使用它并不持怀疑态度。但是,我仍然想知道对数组进行重复数据删除的最短所有浏览器兼容方法。
集合是新规范的一部分,您应该使用排序/减少组合来确保跨浏览器兼容性@AlexanderDixon
.reduce() 不是跨浏览器兼容的,因为我必须应用 poly-fill。不过,我很感谢你的回应。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
答11:
与HuntsBot一起,探索全球自由职业机会–huntsbot.com
最佳答案具有 O(n²) 的复杂性,但这可以通过使用对象作为散列仅使用 O(n) 来完成:
function getDistinctArray(arr) {
var dups = {};
return arr.filter(function(el) {
var hash = el.valueOf();
var isDup = dups[hash];
dups[hash] = true;
return !isDup;
});
}
这适用于字符串、数字和日期。如果您的数组包含对象,则上述解决方案将不起作用,因为当强制转换为字符串时,它们都将具有 “[object Object]” 的值(或类似的值)并且不适合作为查找值。您可以通过在对象本身上设置标志来获得对象的 O(n) 实现:
function getDistinctObjArray(arr) {
var distinctArr = arr.filter(function(el) {
var isDup = el.inArray;
el.inArray = true;
return !isDup;
});
distinctArr.forEach(function(el) {
delete el.inArray;
});
return distinctArr;
}
2019 年编辑: 现代版本的 JavaScript 使这个问题更容易解决。无论您的数组是否包含对象、字符串、数字或任何其他类型,都可以使用 Set。
function getDistinctArray(arr) {
return [...new Set(arr)];
}
实现如此简单,不再需要定义函数。
HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com
您是否考虑过您的方法对性能的影响?
@Tushar - 您的要点给出了 404。没有排序算法具有 O(n) 复杂性。排序不会更快。
@Tushar - 该数组中没有实际的重复项。如果要从数组中删除与数组中其他对象具有完全相同属性和值的对象,则需要编写自定义相等检查函数来支持它。
@Tushar - 此页面上的所有答案都不会从 your gist 中的此类数组中删除任何重复项。
请注意,IE 迟到了 Set
原文链接:https://www.huntsbot.com/qa/bLXd/remove-duplicate-values-from-js-array?lang=zh_CN&from=csdn
huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式