从一个数组中删除包含在另一个数组中的所有元素。

428

我正在寻找一种有效的方法,从JavaScript数组中删除所有在另一个数组中存在的元素。

// If I have this array:
var myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];

// and this one:
var toRemove = ['b', 'c', 'g'];

我希望对myArray进行操作,使其变成这样:['a', 'd', 'e', 'f']

使用jQuery,我正在使用grep()inArray(),这很有效:

myArray = $.grep(myArray, function(value) {
    return $.inArray(value, toRemove) < 0;
});

有没有一种纯JavaScript的方法来做到这一点,而不需要使用循环和切割(splicing)?


无论如何,它总是会涉及到某个层面的循环。 - Blue Skies
1
如果你真的想让它“高效”,你不会使用像.filter()这样的函数式方法。相反,你会使用for循环。如果原始顺序不需要保持,你可以避免使用.splice()。或者,如果你认为有很多项需要删除,也有办法使.splice()更加高效。 - Blue Skies
不错,你的jQuery解决方案很适合我。谢谢。 - EPurpl3
17个回答

667

使用Array.filter()方法:

myArray = myArray.filter( function( el ) {
  return toRemove.indexOf( el ) < 0;
} );

随着浏览器对 Array.includes() 的支持增加,这里有一点小的改进:


myArray = myArray.filter( function( el ) {
  return !toRemove.includes( el );
} );

下一步采用 箭头函数 进行适应:


myArray = myArray.filter( ( el ) => !toRemove.includes( el ) );

32
如果你正在使用Underscore.js,那么可以使用.difference()函数来实现这个功能。 - Bill Criswell
1
@AlecRust将toRemove()的所有元素转换为大写,并在回调函数中将el更改为el.toUpperCase() - Sirko
4
这个顺序不是n的平方吗? - Frazer Kirkman
1
@FrazerKirkman 在非性能关键的环境或只有小问题实例时,我更喜欢代码的可读性而不是微观优化。但你说得对,这是不太理想的。从我的角度来看,我可以想到至少一种 O(n * log n) 的算法。 - Sirko
2
有没有一种方法可以按n的顺序实现这个? - Praveen Kishor
显示剩余12条评论

95

ECMAScript 6 sets可以更快地计算一个数组中不在另一个数组中的元素:

const myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const toRemove = new Set(['b', 'c', 'g']);

const difference = myArray.filter( x => !toRemove.has(x) );

console.log(difference); // ["a", "d", "e", "f"]

自从V8引擎浏览器使用O(1)的lookup complexity以来,整个算法的时间复杂度为O(n)。

7
与使用 .includes 的数组相比,这非常非常快。对于大型数组(约为 50,000 个元素),时间从约 79 秒降至不到 1 秒。感谢您的发布。 - Joshua Pinter

72
var myArray = [
  {name: 'deepak', place: 'bangalore'}, 
  {name: 'chirag', place: 'bangalore'}, 
  {name: 'alok', place: 'berhampur'}, 
  {name: 'chandan', place: 'mumbai'}
];
var toRemove = [
  {name: 'deepak', place: 'bangalore'},
  {name: 'alok', place: 'berhampur'}
];



myArray = myArray.filter(ar => !toRemove.find(rm => (rm.name === ar.name && ar.place === rm.place) ))

请问您能否在这个备受好评的问题上添加一些解释呢? - harmonica141
1
我花了好几个小时去寻找问题的解决方案,终于找到了,真是太棒了。非常感谢你! - Tarvo Mäesepp
1
你帮我在一天内完成了我的Jira,谢谢伙计。 - sam
这对于具有唯一属性的对象数组非常有用。+1 - Qumber
1
我会使用 some() 替代 find(),因为它返回一个布尔值,而不是匹配的项,这需要断言为真或假来满足 filter() 对其谓词参数的期望。 - cthulhu
显示剩余2条评论

43

filter方法应该能解决问题:

const myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const toRemove = ['b', 'c', 'g'];

// ES5 syntax
const filteredArray = myArray.filter(function(x) { 
  return toRemove.indexOf(x) < 0;
});
如果您的toRemove数组很大,这种查找模式可能效率低下。创建一个映射以便查找是O(1)而不是O(n)将更加高效。
const toRemoveMap = toRemove.reduce(
  function(memo, item) {
    memo[item] = memo[item] || true;
    return memo;
  },
  {} // initialize an empty object
);

const filteredArray = myArray.filter(function (x) {
  return toRemoveMap[x];
});

// or, if you want to use ES6-style arrow syntax:
const toRemoveMap = toRemove.reduce((memo, item) => ({
  ...memo,
  [item]: true
}), {});

const filteredArray = myArray.filter(x => toRemoveMap[x]);

我真的很喜欢这个答案…… 我只想指出最后一行应该返回const filteredArray = myArray.filter(x => !toRemoveMap[x]); // 而不是toRemoveMap[x] - Ruslan Gonzalez

27
如果您正在使用一个对象数组,那么下面的代码应该可以实现去除重复项的操作,其中对象属性将是删除重复项的标准。
在下面的例子中,通过比较每个项目的名称来删除重复项。
请尝试这个例子。 http://jsfiddle.net/deepak7641/zLj133rh/

var myArray = [
  {name: 'deepak', place: 'bangalore'}, 
  {name: 'chirag', place: 'bangalore'}, 
  {name: 'alok', place: 'berhampur'}, 
  {name: 'chandan', place: 'mumbai'}
];
var toRemove = [
  {name: 'deepak', place: 'bangalore'},
  {name: 'alok', place: 'berhampur'}
];

for( var i=myArray.length - 1; i>=0; i--){
  for( var j=0; j<toRemove.length; j++){
      if(myArray[i] && (myArray[i].name === toRemove[j].name)){
      myArray.splice(i, 1);
     }
    }
}

alert(JSON.stringify(myArray));


15

14

最简单的如何呢:

var myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
var toRemove = ['b', 'c', 'g'];

var myArray = myArray.filter((item) => !toRemove.includes(item));
console.log(myArray)


2
请记住,在ES7之前,includes方法是不可用的。 - Greg

10

我刚刚实现了如下:

Array.prototype.exclude = function(list){
        return this.filter(function(el){return list.indexOf(el)<0;})
}

使用方法:

myArray.exclude(toRemove);

3
扩展原生对象的prototype(如Array)并不是一个好的做法。这可能会与未来语言发展产生长期冲突(请参见“flatten案例”)。 - MarcoL

8

你可以使用lodash的_.differenceBy

const myArray = [
  {name: 'deepak', place: 'bangalore'}, 
  {name: 'chirag', place: 'bangalore'}, 
  {name: 'alok', place: 'berhampur'}, 
  {name: 'chandan', place: 'mumbai'}
];
const toRemove = [
  {name: 'deepak', place: 'bangalore'},
  {name: 'alok', place: 'berhampur'}
];
const sorted = _.differenceBy(myArray, toRemove, 'name');

这里是示例代码:CodePen


1
如果属性嵌套在对象内部怎么办?就像你的情况一样,有一个类似测试的东西 {name: 'deepak',place: 'bangalore',nested:{test:1}} - Charith Jayasanka

8

如果你不能使用像filter这样的新ES5功能,那么我认为你需要使用两个循环:

for( var i =myArray.length - 1; i>=0; i--){
  for( var j=0; j<toRemove.length; j++){
    if(myArray[i] === toRemove[j]){
      myArray.splice(i, 1);
    }
  }
}

筛选器不是“新的HTML5东西”。 - goofballLogic
我应该写“ES5的东西”。它在ES3中不可用。 - MarcoL

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