乐闻世界logo
搜索文章和话题

How to remove duplicate values from js array

7 个月前提问
3 个月前修改
浏览次数35

7个答案

1
2
3
4
5
6
7

在JavaScript中,从数组中删除重复值可以通过几种不同的方法实现。以下是一些常见的方法,每种方法都有其自身的优势。

1. 使用Set对象

Set是ES6中引入的一个新的数据结构,它允许你存储唯一值(重复的元素会被忽略)。我们可以利用这个特性来删除数组中的重复值。

javascript
const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = [...new Set(array)]; console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

使用Set对象是最简洁的方法,代码易于理解,且性能良好。

2. 使用filter方法

Array.prototype.filter方法可以用来遍历数组并返回一个新数组,包含所有通过测试函数的元素。我们可以利用这个方法来筛选出第一次出现的元素,从而达到去重的效果。

javascript
const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.filter((item, index, arr) => arr.indexOf(item) === index); console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

这种方法不需要任何外部库或特定的语言特性,因此适用于老版本的JavaScript环境。

3. 使用reduce方法

Array.prototype.reduce方法对数组中的每个元素执行一个由您提供的“reducer”函数,将其结果汇总为单个返回值。我们可以用它来构建一个不包含重复值的数组。

javascript
const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.reduce((acc, current) => { if (acc.indexOf(current) === -1) { acc.push(current); } return acc; }, []); console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

这种方法给了我们更多的控制力,但它的效率可能不如使用Set对象。

4. 使用forEach方法和辅助对象

我们也可以使用forEach遍历数组,并使用一个辅助对象(或者Map)来记录已经出现过的值。

javascript
const array = [1, 2, 2, 3, 4, 4, 5]; let uniqueObject = {}; const uniqueArray = []; array.forEach((item) => { if (!uniqueObject[item]) { uniqueArray.push(item); uniqueObject[item] = true; } }); console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

这种方法的效率也比较高,但是代码稍微复杂一些,并且需要额外的空间来存储辅助对象。


每种方法都有其适用场景,选择哪一种取决于具体需求、代码的可读性以及对旧版JavaScript的支持需求。例如,如果你正在编写一个需要在老版本浏览器上运行的应用程序,你可能需要避免使用Setfilter,而是选择for循环或其他ES5兼容的方法。如果你的环境支持ES6,那么使用Set可能是最简单和最直观的方式。

2024年6月29日 12:07 回复

长话短说

使用Set构造函数和展开语法

shell
uniq = [...new Set(array)];

(请注意 varuniq将是一个数组... new Set()将其转换为集合,但 [...] 再次将其转换回数组)


“聪明”但天真的方式

shell
uniqueArray = a.filter(function(item, pos) { return a.indexOf(item) == pos; })

基本上,我们迭代数组,并针对每个元素检查该元素在数组中的第一个位置是否等于当前位置。显然,对于重复元素来说,这两个位置是不同的。

使用过滤器回调的第三个(“这个数组”)参数,我们可以避免数组变量的闭包:

shell
uniqueArray = a.filter(function(item, pos, self) { return self.indexOf(item) == pos; })

尽管简洁,但该算法对于大型数组(二次时间)并不是特别有效。

哈希表来救援

shell
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}])将返回 just [{foo:1}]

也就是说,如果您的数组仅包含基元并且您不关心类型(例如它始终是数字),则此解决方案是最佳的。

来自两个世界的最好的

通用解决方案结合了这两种方法:它对基元使用哈希查找,对对象使用线性搜索。

shell
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); }); }

排序|独特的

另一种选择是先对数组进行排序,然后删除与前一个元素相等的每个元素:

shell
function uniq(a) { return a.sort().filter(function(item, pos, ary) { return !pos || item != ary[pos - 1]; }); }

同样,这不适用于对象(因为所有对象都相等sort)。此外,我们默默地更改原始数组作为副作用 - 不好!但是,如果您的输入已经排序,那么这就是要走的路(只需sort从上面删除)。

独特之处在于...

有时,需要基于除相等之外的某些标准来唯一化列表,例如,过滤掉不同但共享某些属性的对象。这可以通过传递回调来优雅地完成。此“key”回调应用于每个元素,并且具有相同“key”的元素将被删除。由于key预计返回一个原语,因此哈希表在这里可以正常工作:

shell
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删除物理上不同但“看起来”相同的对象:

shell
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不是原始的,则必须诉诸线性搜索:

shell
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

shell
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

shell
function uniqBy(a, key) { return [ ...new Map( a.map(x => [key(x), x]) ).values() ] }

它们都可以使用非原始键。

第一个还是最后一个?

当通过键删除对象时,您可能希望保留第一个“相等”对象或最后一个对象。

使用Set上面的变体保留第一个,使用Map保留最后一个:

shell
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))

运行代码片段Hide results

展开片段

图书馆

underscoreLo-Dash都提供了uniq方法。他们的算法基本上类似于上面的第一个片段,归结为:

shell
var result = []; a.forEach(function(item) { if(result.indexOf(item) < 0) { result.push(item); } });

这是二次的,但还有一些额外的好处,例如包装 native indexOf、通过键进行统一的能力(iteratee用他们的说法)以及对已排序数组的优化。

如果你正在使用 jQuery 并且不能忍受没有一美元的任何东西,它会像这样:

shell
$.uniqArray = function(a) { return $.grep(a, function(item, pos) { return $.inArray(item, a) === pos; }); }

这又是第一个片段的变体。

表现

JavaScript 中函数调用的开销很大,因此上述解决方案虽然简洁,但效率并不是特别高。为了获得最佳性能,请替换filter为循环并摆脱其他函数调用:

shell
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 核心人员做得很好!)

显示代码片段

shell
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)

Run code snippetHide results

Expand snippet

ES6

ES6 提供了Set对象,这使得事情变得更加容易:

shell
function uniq(a) { return Array.from(new Set(a)); }

或者

shell
let uniq = a => [...new Set(a)];

请注意,与 python 不同,ES6 集合按插入顺序迭代,因此此代码保留原始数组的顺序。

但是,如果您需要一个具有唯一元素的数组,为什么不从一开始就使用集合呢?

发电机

uniq可以在相同的基础上构建“惰性”、基于生成器的版本:

  • 从参数中获取下一个值

  • 如果已经看过,请跳过

  • 否则,产生它并将其添加到已经看到的值集中

    function* uniqIter(a) { let seen = new Set();

    shell
    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

运行代码片段Hide results

展开片段

2024年6月29日 12:07 回复

使用 jQuery 又快又脏:

shell
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"]; var uniqueNames = []; $.each(names, function(i, el){ if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el); });
2024年6月29日 12:07 回复

厌倦了看到所有使用 for 循环或 jQuery 的糟糕例子。如今,Javascript 拥有完美的工具:排序、映射和归约。

Uniq 在保持现有顺序的同时减少

shell
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 排序

可能还有更快的方法,但这个方法相当不错。

shell
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,这使得删除所有重复项变得非常容易且高效:

shell
var uniq = [ ...new Set(names) ]; // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]

根据出现次数排序:

有人询问如何根据唯一名称的数量对结果进行排序:

shell
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)
2024年6月29日 12:07 回复

Vanilla JS:使用像集合这样的对象删除重复项

您始终可以尝试将其放入对象中,然后迭代其键:

shell
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:通过跟踪已经看到的值来删除重复项(订单安全)

或者,对于顺序安全版本,使用一个对象来存储所有以前看到的值,并在添加到数组之前检查它的值。

shell
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按插入顺序返回元素。

shell
function remove_duplicates_es6(arr) { let s = new Set(arr); let it = s.values(); return Array.from(it); }

用法示例:

shell
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"]
2024年6月29日 12:07 回复

.filter使用数组和函数的单行版本.indexOf

shell
arr = arr.filter(function (value, index, array) { return array.indexOf(value) === index; });

ES6方法

shell
arr = arr.filter((value, index, array) => array.indexOf(value) === index )
2024年6月29日 12:07 回复

在JavaScript中,可以使用多种方法从数组中删除重复的值。

  1. 使用 Set Set 是一种允许你存储任何类型唯一值的集合。因此,你可以利用 Set 来轻松去重。

    javascript
    const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = [...new Set(array)]; console.log(uniqueArray); // [1, 2, 3, 4, 5]
  2. 使用 filter() filter() 方法可以用来创建一个新数组,这个新数组由通过所提供函数测试的所有元素组成。这里我们可以检查当前元素的索引是否是它首次出现的索引。

    javascript
    const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.filter((item, index, arr) => arr.indexOf(item) === index); console.log(uniqueArray); // [1, 2, 3, 4, 5]
  3. 使用 reduce() reduce() 方法对数组中的每个元素执行一个由你提供的“reducer”回调函数,将其结果汇总为单个返回值。你可以使用它来构建一个不包含重复项的新数组。

    javascript
    const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.reduce((accumulator, current) => { if (accumulator.indexOf(current) === -1) { accumulator.push(current); } return accumulator; }, []); console.log(uniqueArray); // [1, 2, 3, 4, 5]
  4. 使用 forEach() 和 includes() 你可以遍历数组并使用 includes() 方法检查结果数组中是否已经包含了当前项。

    javascript
    const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = []; array.forEach(item => { if (!uniqueArray.includes(item)) { uniqueArray.push(item); } }); console.log(uniqueArray); // [1, 2, 3, 4, 5]

以上是一些常见的方法来去除数组中的重复项。你可以根据实际情况选择最适合你的方法。通常情况下,使用 Set 是最简单快捷的方式。

2024年6月29日 12:07 回复

你的答案