在Javascript/jQuery中从数组中删除多个元素

175

我有两个数组。第一个数组包含一些值,而第二个数组包含应从第一个数组中删除的值的索引。例如:

var valuesArr = new Array("v1","v2","v3","v4","v5");   
var removeValFromIndex = new Array(0,2,4);

我想从valuesArr中删除索引为0,2,4的值。 我认为使用内置的splice方法可能有所帮助,所以我想到了以下方法:

$.each(removeValFromIndex,function(index,value){
    valuesArr.splice(value,1);
});

但这并没有起作用,因为在每次splice之后,valuesArr中的值的索引都会改变。我可以通过使用临时数组并将所有值复制到第二个数组来解决这个问题,但我想知道是否有任何本地方法可以传递多个索引以从数组中删除值。

我更喜欢使用jQuery解决方案。(不确定是否可以在这里使用grep

27个回答

338

你始终可以使用老式的普通 for 循环:

var valuesArr = ["v1","v2","v3","v4","v5"],
    removeValFromIndex = [0,2,4];    

for (var i = removeValFromIndex.length -1; i >= 0; i--)
   valuesArr.splice(removeValFromIndex[i],1);

倒序遍历removeValFromIndex数组,这样你就可以使用.splice()而不会搞乱尚未删除项的索引。

请注意,在上面的示例中,我使用了方括号的数组字面量语法来声明两个数组。这是推荐的语法,因为使用new Array()可能会令人困惑,因为其响应方式取决于传入的参数数量。

编辑:刚看到您在另一个答案中的评论,指出索引数组不一定按任何特定顺序排列。如果确实如此,请在开始之前将其降序排序:

removeValFromIndex.sort(function(a,b){ return b - a; });

然后跟上您喜欢的任何循环/$.each()/等方法。


4
切片操作会影响索引吗? - Muhammad Umer
8
不,只要你正确操作,这正是我的回答所解释的。 - nnnnnn
5
感谢提醒倒序问题。 - Daniel Nalbach
5
+1,我没有意识到我必须按相反的顺序进行拼接,尽管我的方法使用的是$.each(rvm.reverse(), function(e, i ) {})而不是forEach。 - Luis Stanley Jovel
3
只有当 removeValFromIndex 按升序排序时,这才有效。 - Kunal Burangi
显示剩余8条评论

54

我建议您使用Array.prototype.filter

var valuesArr = ["v1","v2","v3","v4","v5"];
var removeValFrom = [0, 2, 4];
valuesArr = valuesArr.filter(function(value, index) {
     return removeValFrom.indexOf(index) == -1;
})

1
在 filter 中使用 indexOf 并不是最优选择。 - Alvaro Silvino
4
根据 https://jsperf.com/remove-multiple/1 的测试结果,upvote 方法比 splice 方法更快速。 - lionbigcat
是的,它比splice更快,但会创建一个新数组,而不是在原地删除项目。 - doc_id

37

这是我在不使用 lodash/underscore 时使用的一个函数:

while(IndexesToBeRemoved.length) {
    elements.splice(IndexesToBeRemoved.pop(), 1);
}

优雅的解决方案!起初我认为这不会起作用,因为我认为每次调用slice时都必须重新计算要删除的索引(在IndexestoBeRemoved上减1),但实际上它确实起作用了! - Renato Gama
3
太聪明了。 - Farzad Yousefzadeh
22
只有当IndexesToBeRemoved数组按升序排序时,此解决方案才有效。 - xfg
2
这些索引将在第一次切片后失效。 - shinzou
@shinzou - 如果 IndexesToBeRemoved 已经排序(升序),那么就不需要这样做。 - nnnnnn
每次重建数组的时间复杂度大概是O(array.length),而你要这样做indices.length次(其中array.length线性递减),因此这个解决方案的计算成本可能是O(array.length*indices.length)(或者确切地说是O(array.length*indices.length/(1+indices.length/array.length)),我想)。肯定有一种方法只遍历一次array - Will Chen

18

不是 in-place,但可以使用 jQuerygrepinArray 函数来实现。

var arr = $.grep(valuesArr, function(n, i) {
    return $.inArray(i, removeValFromIndex) ==-1;
});

alert(arr);//arr contains V2, V4

看看这个示例。


如果您只是说valuesArr = $.grep(...);,那就足以接近原地修改。 - nnnnnn
1
@nnnnnn 哈哈哈,我要去开会了(你知道他们让你变得更有生产力),所以没有进行太多的实验。 - TheVillageIdiot
1
@cept0 为什么要给负投票?楼主要求 jQuery 的解决方案。 - Ste77
jsfiddler - 错误404。非常抱歉,该页面不存在。 - Ash

18

使用filterSet的简单高效(线性复杂度)解决方案:

const valuesArr = ['v1', 'v2', 'v3', 'v4', 'v5'];   
const removeValFromIndex = [0, 2, 4];

const indexSet = new Set(removeValFromIndex);

const arrayWithValuesRemoved = valuesArr.filter((value, i) => !indexSet.has(i));

console.log(arrayWithValuesRemoved);

那种实现的巨大优势在于集合查找操作(has函数)只需要常数时间,比如说比nevace的答案更快。

之前的代码中,yes在使用数组时出现了问题。我尝试了[...new Set(xyz)]这个方法。 - Om Fuke

11

这对我来说很有效,也适用于从对象数组中删除时:

var array = [ 
    { id: 1, name: 'bob', faveColor: 'blue' }, 
    { id: 2, name: 'jane', faveColor: 'red' }, 
    { id: 3, name: 'sam', faveColor: 'blue' }
];

// remove people that like blue

array.filter(x => x.faveColor === 'blue').forEach(x => array.splice(array.indexOf(x), 1));

可能有一种更短更有效的方法来编写这个,但这样也可以工作。


10

我觉得有必要用 O(n) 时间发布一个答案 :). splice 解决方案的问题在于,由于数组的底层实现实际上是 真正的数组,每个 splice 调用都需要花费 O(n) 的时间。当我们设置一个例子来利用这种行为时,这种情况最为明显:

var n = 100
var xs = []
for(var i=0; i<n;i++)
  xs.push(i)
var is = []
for(var i=n/2-1; i>=0;i--)
  is.push(i)

这会从中间开始移除元素,因此每次移除都会强制js引擎复制n/2个元素,我们总共有(n/2)^2个复制操作,这是二次的。

splice解决方案(假设is已按降序排序以消除开销)如下:

for(var i=0; i<is.length; i++)
  xs.splice(is[i], 1)

然而,实现线性时间解决方案并不难,只需从头开始重建数组,使用掩码来确定是否复制元素(排序会将其推向O(n)log(n))。以下是这样的一种实现方式(注意mask为了速度被布尔反转):

var mask = new Array(xs.length)
for(var i=is.length - 1; i>=0; i--)
  mask[is[i]] = true
var offset = 0
for(var i=0; i<xs.length; i++){
  if(mask[i] === undefined){
    xs[offset] = xs[i]
    offset++
  }
}
xs.length = offset

我在 jsperf.com 上测试了这个东西,即使 n=100,切片方法也慢了整整90%。对于更大的 n,这个差距会更大。


9
我认为这是最优雅的解决方案:
const oldArray = [1, 2, 3, 4, 5]
const removeItems = [1, 3, 5]

const newArray = oldArray.filter((value) => {
    return !removeItems.includes(value)
})

console.log(newArray)

输出:

[2, 4]

甚至更短:

const newArray = oldArray.filter(v => !removeItems.includes(v))

感谢简洁明了! - cNgamba

7
function filtermethod(element, index, array) {  
    return removeValFromIndex.find(index)
}  
var result = valuesArr.filter(filtermethod);

MDN参考文档在这里



@riship89 这个 fiddle 挂了。 - mate64
@Karna:fiddle宕机了。 - hrishikeshp19
1
请注意,撰写本文时(2014年6月),Array.prototype.find是当前ES6草案的一部分,并且仅在当前火狐浏览器中实现。 - Olli K

6

在纯JS中,您可以反向遍历数组,因此splice()不会破坏循环中下一个元素的索引:

for (var i = arr.length - 1; i >= 0; i--) {
    if ( yuck(arr[i]) ) {
        arr.splice(i, 1);
    }
}

不起作用,yuck 不是一个函数,因此假定它是不需要的元素索引的索引,并使用 yuck[arr[i]]。 - DavChana

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