如何在JavaScript中从数组创建一个Set并删除原始项目

6

我有一个带有重复值的数组。

我想创建一个Set来获取该数组的不同值,并删除或创建一个新的数组,该数组将具有减去创建Set所需元素的相同数据。

这不仅仅是删除重复项,而是删除原始数组中每个不同值的单个条目

像这样的东西可以工作,但我想知道是否有更直接的方法:

let originalValues = [
  'a',
  'a',
  'a',
  'b',
  'b',
  'c',
  'c',
  'd'
];

let distinct = new Set(originalValues);
/*
distinct -> { 'a', 'b', 'c', 'd' }
*/

// Perhaps originalValues.extract(distinct) ??
for (let val of distinct.values()) {
  const index = originalValues.indexOf(val);
  originalValues.splice(index, 1);
}

/* 
originalValues -> [
  'a',
  'a',
  'b',
  'c'
];
*/

1
如果 originalValues'a', 'a', 'a' 开头,它应该以 'a', 'a' 结尾还是只有一个 'a' - Ry-
@LucasRicoy 顺序重要吗?哪一个应该被移除? - Oriol
1
重新打开,因为这是关于删除集合中出现的元素,而不是关于删除重复项的。 - Oriol
1
originalValues.splice(index, 1);的翻译是什么? - dandavis
1
在那段代码中,要注意不要删除索引为“-1”的元素,否则会让你的一天都毁了! - dandavis
显示剩余3条评论
6个回答

5
使用Array#filterSet结合使用:

const originalValues = ['a', 'a', 'a', 'b', 'b', 'c',  'c', 'd'];

const remainingValues = originalValues.filter(function(val) {
  if (this.has(val)) { // if the Set has the value
    this.delete(val); // remove it from the Set
    return false; // filter it out
  }

  return true;
}, new Set(originalValues));



console.log(remainingValues);


谢谢@Rajesh,我会记住的。我之前的思路有问题,所以无法清楚地表达我的观点。下次我会更加努力尝试 :) - Lucas Ricoy
1
@Rajesh - 如果你不想复杂度为O(n^2),那么使用Set会更好,因为Array#indexOf每次都会迭代originalValues - Ori Drori
如果性能是一个问题,我建议使用普通对象而不是Set,因为创建集合必然会有自己的性能成本。 - Andrew

3
你可以使用闭包来检查 Set 中的存在性。

let originalValues = ['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd'],
    result = originalValues.filter((s => a => s.has(a) || !s.add(a))(new Set));

console.log(result);


2

在循环中不应使用 indexOf,因为它的成本是线性的,总成本会变成二次方。我会使用 map 来计算数组中每个元素出现的次数,然后再将其转换回一个数组,减去一次出现的次数。

let originalValues = ['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd'];
let freq = new Map(); // frequency table
for (let item of originalValues)
  if (freq.has(item)) freq.set(item, freq.get(item)+1);
  else freq.set(item, 1);
var arr = [];
for (let [item,count] of freq)
  for (let i=1; i<count; ++i)
    arr.push(item);
console.log(arr);

如果所有项都是字符串,您可以使用普通对象而不是映射。


@d仅出现一次,并且一个出现已被删除。因此这是可以预料的。 - Oriol
我猜不是通过看评论来判断的“这是关于删除出现在集合中的元素,而不是关于删除重复项”的。d只出现一次,所以根本不符合标准。 - Farooq Ahmed Khan
没错!你的答案很好地使用了频率表来解决问题。而且指出在循环内部使用indexOf的用法也很敏锐。 - Lucas Ricoy

1
你可以使用 hash table 创建一个简单的 Array.prototype.reduce 循环来计算出现次数,并仅在出现超过一次时填充 result
请参见下面的演示:

var originalValues=['a','a','a','a','b','b','b','c','c','d'];

var result = originalValues.reduce(function(hash) {
  return function(p,c) {
    hash[c] = (hash[c] || 0) + 1;
    if(hash[c] > 1)
      p.push(c);
    return p;  
  };     
}(Object.create(null)), []);

console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}


1

你可以使用reduce()创建一个新数组,并将原始数组中的唯一值更新到新数组,而不是使用Set。

let oV = ["a", "a", "a", "a", "b", "b", "c", "c", "d"]

var o = {}
var distinct = oV.reduce(function(r, e) {
  if (!o[e]) o[e] = 1 && r.push(e) && oV.splice(oV.indexOf(e), 1)
  return r;
}, [])

console.log(distinct)
console.log(oV)


1
作为另一种方法,您可以使用以下算法仅删除重复元素的第一个条目。如果不是重复的,则不会删除任何内容。

const originalValues = ['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd'];

var r = originalValues.reduce(function(p, c, i, a) {
  var lIndex = a.lastIndexOf(c);
  var index = a.indexOf(c)
  if (lIndex === index || index !== i)
    p.push(c);
  return p
}, [])

console.log(r)


如果重复项不区分大小写,则可以直接删除第一次迭代。

const originalValues = ['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd'];

var r = originalValues.filter(function(el, i) {
  return originalValues.indexOf(el) !== i
})

console.log(r)


1
如果您不使用它,请勿向filter()传递自定义的this,这只会减慢速度...另外,第三个回调参数可以避免使用闭包,使您的回调变得通用和可重用。 - dandavis
@dandavis 感谢您指出这个问题。我复制了最初使用 .reduce 的代码,但忘记删除 []。我已经更新了我的答案。 - Rajesh

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