173

I have two arrays. The first array contains some values while the second array contains indices of the values which should be removed from the first array. For example:

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

I want to remove the values present at indices 0,2,4 from valuesArr. I thought the native splice method might help so I came up with:

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

But it didn't work because after each splice, the indices of the values in valuesArr were different. I could solve this problem by using a temporary array and copying all values to the second array, but I was wondering if there are any native methods to which we can pass multiple indices at which to remove values from an array.

I would prefer a jQuery solution. (Not sure if I can use grep here)

Jeremy
  • 1
  • 85
  • 340
  • 366
Ajinkya
  • 22,324
  • 33
  • 110
  • 161

27 Answers27

336

There's always the plain old for loop:

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

Go through removeValFromIndex in reverse order and you can .splice() without messing up the indexes of the yet-to-be-removed items.

Note in the above I've used the array-literal syntax with square brackets to declare the two arrays. This is the recommended syntax because new Array() use is potentially confusing given that it responds differently depending on how many parameters you pass in.

EDIT: Just saw your comment on another answer about the array of indexes not necessarily being in any particular order. If that's the case just sort it into descending order before you start:

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

And follow that with whatever looping / $.each() / etc. method you like.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • 4
    wont slicing mess up the index – Muhammad Umer Aug 29 '13 at 15:42
  • 8
    @MuhammadUmer - No, not if you do it correctly, which is what my answer explains. – nnnnnn Aug 29 '13 at 19:09
  • so forEach it'd be this ..`var bigarr = [3,4,6,7,8,2,4,7,9] var rmv = [1,5]; rmv.forEach(function(e,i){bigarr.splice(rmv[rmv.length-i-1],1);});` – Muhammad Umer Aug 29 '13 at 19:34
  • `function del(x,y){y.forEach(function(e,i){x.splice(y[y.length-i-1],1);});}` – Muhammad Umer Aug 29 '13 at 19:36
  • @MuhammadUmer - I guess that would work, but I wouldn't use a _forEach_ unless I intended to use the _e_ parameter, so I'd sort the array in descending order and then use the _forEach_ without doing arithmetic on _i_ - note that the OP said that the values in the index array might start out of order so they'd have to be sorted anyway... – nnnnnn Aug 29 '13 at 21:34
  • 5
    +1, I didn't realized I had to do the splice in reverse order, although instead of using forEach, my approach is using `$.each(rvm.reverse(), function(e, i ) {})` – Luis Stanley Jovel Nov 03 '16 at 15:57
  • 3
    this will only work if `removeValFromIndex ` is in sorted in ascending order – Kunal Burangi Aug 21 '19 at 05:06
  • 1
    @KunalBurangi - the answer explicitly covers sorting. – nnnnnn Aug 21 '19 at 08:14
  • removeValFromIndex sorting should be ascending, not descending.[0, 2 ,4] like at the original answer, but the NOTE says "descending" that's wrong – ElRey777 Jul 17 '20 at 17:30
  • @ElRey777 - My edit note says *descending* order because it also says to follow that with `$.each()`. That is, sort descending if using a "conventional" ascending index loop. – nnnnnn Jul 18 '20 at 00:41
  • @nnnnnn ok thanks for the clarification seems I got this EDIT part wrong – ElRey777 Jul 18 '20 at 03:39
  • 1
    I would like to draw attention to how inefficient this repeated-splicing method is, as explained here: https://stackoverflow.com/a/56824017/93910 . In short, there's a simple algorithm where you don't need to shift all the later items each time you remove one! – Sanjay Manohar Aug 31 '20 at 00:06
53

I suggest you use 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;
})
Sasha Davydenko
  • 794
  • 7
  • 12
37

Here is one that I use when not going with lodash/underscore:

while(IndexesToBeRemoved.length) {
    elements.splice(IndexesToBeRemoved.pop(), 1);
}
Renato Gama
  • 16,431
  • 12
  • 58
  • 92
Dan Ochiana
  • 3,340
  • 1
  • 30
  • 28
  • Elegant solution! At first I thought this wouldn't work because I thought that every time you call `slice` you would have to recalculate the indexes to be removed (-1 on `IndexestoBeRemoved`), but it actually works! – Renato Gama May 27 '16 at 16:02
  • 3
  • 22
    This solution is worked if only `IndexesToBeRemoved` array is sorted by ascending. – xfg Jan 30 '17 at 03:55
  • 2
    Those indices will be invalidated after the first splice. – shinzou Jan 03 '18 at 18:42
  • @shinzou - Not if `IndexesToBeRemoved` is sorted (ascending). – nnnnnn Nov 18 '18 at 01:06
  • Rebuilding the array each time is presumably `O(array.length)`, and you're doing that `indices.length` times (with `array.length` decreasing linearly), so this solution is probably about `O(array.length*indices.length)` in computational cost (or `O(array.length*indices.length/(1+indices.length/array.length))` exactly, I think). Surely there has to be a way to go through `array` only once. – Will Chen Jul 03 '20 at 02:59
18

A simple and efficient (linear complexity) solution using filter and Set:

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

The great advantage of that implementation is that the Set lookup operation (has function) takes a constant time, being faster than nevace's answer, for example.

Alberto Trindade Tavares
  • 10,056
  • 5
  • 38
  • 46
18

Not in-place but can be done using grep and inArray functions of jQuery.

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

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

check this fiddle.

TheVillageIdiot
  • 40,053
  • 20
  • 133
  • 188
11

This works well for me and work when deleting from an array of objects too:

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

There might be a shorter more effecient way to write this but this does work.

StuartMc
  • 666
  • 7
  • 12
10

It feels necessary to post an answer with O(n) time :). The problem with the splice solution is that due to the underlying implementation of array being literally an array, each splice call will take O(n) time. This is most pronounced when we setup an example to exploit this behavior:

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)

This removes elements starting from the middle to the start, hence each remove forces the js engine to copy n/2 elements, we have (n/2)^2 copy operations in total which is quadratic.

The splice solution (assuming is is already sorted in decreasing order to get rid of overheads) goes like this:

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

However, it is not hard to implement a linear time solution, by re-constructing the array from scratch, using a mask to see if we copy elements or not (sort will push this to O(n)log(n)). The following is such an implementation (not that mask is boolean inverted for speed):

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

I ran this on jsperf.com and for even n=100 the splice method is a full 90% slower. For larger n this difference will be much greater.

Community
  • 1
  • 1
simonzack
  • 19,729
  • 13
  • 73
  • 118
8

I find this the most elegant solution:

const oldArray = [1, 2, 3, 4, 5]
const removeItems = [1, 3, 5]

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

console.log(newArray)

output:

[2, 4]

or even shorter:

const newArray = oldArray.filter(v => !removeItems.includes(v))
Artur Müller Romanov
  • 4,417
  • 10
  • 73
  • 132
7
function filtermethod(element, index, array) {  
    return removeValFromIndex.find(index)
}  
var result = valuesArr.filter(filtermethod);

MDN reference is here

hrishikeshp19
  • 8,838
  • 26
  • 78
  • 141
6

In pure JS you can loop through the array backwards, so splice() will not mess up indices of the elements next in the loop:

for (var i = arr.length - 1; i >= 0; i--) {
    if ( yuck(arr[i]) ) {
        arr.splice(i, 1);
    }
}
Watchduck
  • 1,076
  • 1
  • 9
  • 29
  • Does not work yuck is not a function, so assumed it is the index of unwanted element indexes, & used yuck[arr[i]] – DavChana Oct 26 '16 at 14:00
5

A simple solution using ES5. This seems more appropriate for most applications nowadays, since many do no longer want to rely on jQuery etc.

When the indexes to be removed are sorted in ascending order:

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

removeValFromIndex.reverse().forEach(function(index) {
  valuesArr.splice(index, 1);
});

When the indexes to be removed are not sorted:

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

removeValFromIndex.sort(function(a, b) { return b - a; }).forEach(function(index) {
  valuesArr.splice(index, 1);
});
Kaspar Fenner
  • 161
  • 2
  • 7
5

Quick ES6 one liner:

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

const arrayWithValuesRemoved = valuesArr.filter((value, i) => removeValFromIndex.includes(i))
nevace
  • 757
  • 6
  • 6
2

If you are using underscore.js, you can use _.filter() to solve your problem.

var valuesArr = new Array("v1","v2","v3","v4","v5");
var removeValFromIndex = new Array(0,2,4);
var filteredArr = _.filter(valuesArr, function(item, index){
                  return !_.contains(removeValFromIndex, index);
                });

Additionally, if you are trying to remove items using a list of items instead of indexes, you can simply use _.without(), like so:

var valuesArr = new Array("v1","v2","v3","v4","v5");
var filteredArr = _.without(valuesArr, "V1", "V3");

Now filteredArr should be ["V2", "V4", "V5"]

programking
  • 1,376
  • 1
  • 17
  • 32
Johnny Zhao
  • 2,858
  • 2
  • 29
  • 26
  • How contains is implemented in underscore... be careful if it's equivalent to indexOf inside of filter... not optimum at all... – Alvaro Silvino Jan 14 '18 at 00:11
1

Here's one possibility:

valuesArr = removeValFromIndex.reduceRight(function (arr, it) {
    arr.splice(it, 1);
    return arr;
}, valuesArr.sort(function (a, b) { return b - a }));

Example on jsFiddle

MDN on Array.prototype.reduceRight

Olli K
  • 1,720
  • 1
  • 16
  • 17
1

filter + indexOf (IE9+):

function removeMany(array, indexes) {
  return array.filter(function(_, idx) {
    return indexes.indexOf(idx) === -1;
  });
}); 

Or with ES6 filter + find (Edge+):

function removeMany(array, indexes = []) {
  return array.filter((_, idx) => indexes.indexOf(idx) === -1)
}
daviestar
  • 4,531
  • 3
  • 29
  • 47
1

Here's a quickie.

function removeFromArray(arr, toRemove){
    return arr.filter(item => toRemove.indexOf(item) === -1)
}

const arr1 = [1, 2, 3, 4, 5, 6, 7]
const arr2 = removeFromArray(arr1, [2, 4, 6]) // [1,3,5,7]
1

Try this

var valuesArr = new Array("v1", "v2", "v3", "v4", "v5");
console.info("Before valuesArr = " + valuesArr);
var removeValFromIndex = new Array(0, 2, 4);
valuesArr = valuesArr.filter((val, index) => {
  return !removeValFromIndex.includes(index);
})
console.info("After valuesArr = " + valuesArr);
pathe.kiran
  • 2,444
  • 1
  • 21
  • 27
1

You can correct your code by replacing removeValFromIndex with removeValFromIndex.reverse(). If that array is not guaranteed to use ascending order, you can instead use removeValFromIndex.sort(function(a, b) { return b - a }).

minopret
  • 4,726
  • 21
  • 34
  • Looks good to me - http://jsfiddle.net/mrtsherman/gDcFu/2/. Although this makes the supposition that the removal list is in order. – mrtsherman Feb 24 '12 at 03:42
  • @minopret: Thanks but it will only work if indexes in `removeValFromIndex` are in ascending order. – Ajinkya Feb 24 '12 at 03:53
0
var valuesArr = new Array("v1","v2","v3","v4","v5");   
var removeValFromIndex = new Array(0,2,4);

console.log(valuesArr)
let arr2 = [];

for (let i = 0; i < valuesArr.length; i++){
  if (    //could also just imput this below instead of index value
    valuesArr[i] !== valuesArr[0] && // "v1" <--
    valuesArr[i] !== valuesArr[2] && // "v3" <--
    valuesArr[i] !== valuesArr[4]    // "v5" <--
  ){
    arr2.push(valuesArr[i]);
  }
}

console.log(arr2);

This works. However, you would make a new array in the process. Not sure if thats would you want or not, but technically it would be an array containing only the values you wanted.

0

You can try Lodash js library functions (_.forEach(), _.remove()). I was using this technique to remove multiple rows from the table.

let valuesArr = [
    {id: 1, name: "dog"}, 
    {id: 2, name: "cat"}, 
    {id: 3, name: "rat"}, 
    {id: 4, name: "bat"},
    {id: 5, name: "pig"},
]; 
let removeValFromIndex = [
    {id: 2, name: "cat"}, 
    {id: 5, name: "pig"},
]; 
_.forEach(removeValFromIndex, (indi) => {
    _.remove(valuesArr, (item) => {
        return item.id === indi.id;
    });
})
console.log(valuesArr)
/*[
    {id: 1, name: "dog"},  
    {id: 3, name: "rat"}, 
    {id: 4, name: "bat"},
];*/ 

Don't forget to clone (_.clone(valuesArr) or [...valuesArr]) before mutate your array

myeongkil kim
  • 2,465
  • 4
  • 16
  • 22
Egils
  • 1
  • 2
0

This is more of a tweak to the answer by @nnnnnn, but I would also remove potential duplicate indices as this will otherwise cause additional rows to be deleted:

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

function arrayDeleteMulti(arr, idxSet) {

    idxSet = arrayUnique(idxSet).sort(function(a,b){ return a-b; });
    
    for (var i = idxSet.length -1; i >= 0; i--)
        arr.splice(idxSet[i],1);

    return arr
}

So in this test

var arr = [`v0`, `v1`, `v2`, `v3`, `v4`, `v5`];
console.log(arrayDeleteMulti(arr, [4,4,4,0,2]))

We do get

[ "v1", "v3", "v5" ]
Randhir Rawatlal
  • 365
  • 1
  • 3
  • 13
0

An alternative is to create a reducer, validate it by the index that returns the non-selected values and then order it.

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

  const data = valuesArr
    .reduce((p, c, i) => (!r.includes(i) ? [...p, c] : p), [])
    .sort((a, b) => b - a);

  console.log(data);
Jackson Quintero
  • 178
  • 1
  • 1
  • 8
0

Sounds like Apply could be what you are looking for.
maybe something like this would work?

Array.prototype.splice.apply(valuesArray, removeValFromIndexes );
Rob
  • 3,026
  • 4
  • 30
  • 32
  • But the `.splice()` method is not expecting a list of elements to remove, it is expecting a single index of the element at which to start removing followed by the number of elements to remove... – nnnnnn Feb 24 '12 at 04:06
-1
removeValFromIndex.forEach(function(toRemoveIndex){
    valuesArr.splice(toRemoveIndex,1);
});
Ramesh Vishnoi
  • 1,299
  • 1
  • 12
  • 19
  • 1
    Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Mark Rotteveel Nov 18 '20 at 13:35
-1

You could try and use delete array[index] This won't completely remove the element but rather sets the value to undefined.

Henesnarfel
  • 2,129
  • 1
  • 16
  • 18
-2

For Multiple items or unique item:

I suggest you use Array.prototype.filter

Don't ever use indexOf if you already know the index!:

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

valuesArr = valuesArr.filter(function(value, index) {
     return removeValFrom.indexOf(index) == -1;
}); // BIG O(N*m) where N is length of valuesArr and m is length removeValFrom

Do:

with Hashes... using Array.prototype.map

  var valuesArr = ["v1","v2","v3","v4","v5"];
  var removeValFrom = {};
  ([0, 2, 4]).map(x=>removeValFrom[x]=1); //bild the hash.
  valuesArr = valuesArr.filter(function(value, index) {
      return removeValFrom[index] == 1;
  }); // BIG O(N) where N is valuesArr;
Alvaro Silvino
  • 9,441
  • 12
  • 52
  • 80
-2

You could construct a Set from the array and then create an array from the set.

const array = [1, 1, 2, 3, 5, 5, 1];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // Result: [1, 2, 3, 5]
m02ph3u5
  • 3,022
  • 7
  • 38
  • 51
Mohib
  • 1
  • 1