Remove array item using for...of loop
Asked Answered
A

3

11

I'm trying to edit an array and remove elements that do not meet a certain condition. If I use a reverse for loop combined with .splice(index,n), the code works just fine. I'm stuck at implementing the same using the ES6 for...of loop

let array=[1,2,3];
//reverse for loop
for(var i=array.length - 1; i>=0; i--) {
  if(array[i]>=2) {
    /*Collect element before deleting it.*/
    array.splice(i,1);
  }
} 
console.log(array);
// returns [1]

Using for..of

let array=[1,2,3];
for(let entry of array) {
    if(entry>=2) {
        let index = array.indexOf(entry);

        /*The array is being re-indexed when I apply 
        .splice() - the loop will skip over an index
        when one element of array is removed*/

        array.splice(index, 1);
    }
} 
console.log(array);
//returns [1,3]

Is there a way to achieve this functionality using a for...of loop or do I have to stick to the reverse for loop

Update
I need to collect the entries that don't meet the elements removed by either the filter() or reverse for loop functions to a secondary array.

Assassinate answered 2/9, 2019 at 8:16 Comment(4)
Don't use splice while looping over array, as it mutates original arrayPibgorn
Very convoluted. Use .filter instead, much easierVolny
filter is exactly what you need. It's a bad practice to mutate an array you're iterating on.Emrick
"If I use a reverse for loop combined with .splice(index,n), the code works just fine." That is the correct way to do it. You could also use while like thisPeavy
S
12

You can't reasonably use for-of for this. If you remove the "current" entry during the iteration, you'll skip the next entry, because of the way array iterators are specified (they use the index of the entry). You can see that with this contrived example:

const array = [1, 2, 3];
for (const entry of array) {
    console.log(entry);
    if (entry === 2) {
        array.splice(1, 1);
    }
}
console.log(array);

Notice how there was no loop iteration for entry 3.

I'd suggest either sticking with your reverse for loop or using filter to produce a new array containing only the entries you want to keep.

Using filter:

let array = [1, 2, 3];
array = array.filter(entry => entry < 2);
console.log(array);

I said "reasonably" above because, of course, there's always a way. You could loop through a copy of the array and maintain the index outside it:

const array = [1, 2, 3];
let index = 0;
for (const entry of [...array]) {
    if (entry >= 2) {
        array.splice(index, 1);
    } else {
        ++index;
    }
}
console.log(array);

That doesn't seem reasonable compared to the alternatives, unless of course there are constraints pushing you that way. :-)

Saltworks answered 2/9, 2019 at 8:19 Comment(12)
If you remove the "current" entry during the iteration, you'll skip the next entry, because of the way array iterators are specified (they use the index of the entry). Why for of iterators behave like this? please refer some Mozilla doc. i want to explore it in detail.Luci
I need to do other operations before removing the array element. Can I achieve the same using filter function?. (See updated question).Assassinate
@MobeenSarwar - Because, as I said, they use the index. See the specification.Saltworks
@JuneM3ta - What other operations? You can do whatever you like in the filter callback...Saltworks
@T.J Crowder If I have array = array.filter(function(entry) { return entry<2; }); where array=[1,2,3] can I access the elements 2,3 from within the filter() function before they are filtered from the array? Or, do I have to create a separate filtering function to store the elements that don't match that condition (in a separate array) so that I can use them before I discard them?Assassinate
@T.J.Crowder I have a Node.js server with an object array that acts as a local cache. I need to cleanup/optimize the cache on certain events, thus the need to check the condition of each object then persist the object in a db if the object properties meet a specific condition, otherwise retain the object in the cache.Assassinate
@JuneM3ta - You have two options there: If your filtering function is a closure over array, then of course it can access anything on array that it wants: array = array.filter(function(entry) { console.log(`array[0] = ${array[0]}`); return entry<2; }); If it isn't (you've created it elsewhere, perhaps to reuse it), it can use the third argument the callback receives, which is the array being filtered: function filterCallback(entry, index, array) { console.log(`array[0] = ${array[0]}`); return entry<2; }Saltworks
Let us continue this discussion in chat.Assassinate
@JuneM3ta - I don't think the chat function is useful for discussion related to the question. If you have a follow-up question, I suggest posting it. If you don't understand or need more information about this answer, please just comment here. (Yes, I'm at odds with SO on this. I think pushing relevant conversation to chat is a mistake.)Saltworks
@T.J.Crowder Ok cool :). Can I use the filterCallBack for multiple indices? Keep in mind that the filter function will get rid of multiple elements that don't meet the condition. I need to access each of those elements before filtering.Assassinate
@JuneM3ta - The filter callback will be called for each entry in the array. Again, though, you can access anything in the array you like as shown above.Saltworks
@T.J.Crowder I think I get it now!! Thanks. See this answer for the final solutionAssassinate
A
5

According to @T.J's answer, when using a for...of loop:

If you remove the "current" entry during the iteration, you'll skip the next entry, because of the way array iterators are specified (they use the index of the entry).

This leaves two other options, using a reverse for loop and a filter function. I mentioned earlier that I need to do an operation with the current array element before deleting it.

1. Using .filter() function
and referring to @T.J's Comment

let array = [1,2,3];
let collection = [];

array = array.filter(function(entry) {
    if(entry>=2) {
        collection.push(entry);
    } 
    return entry <2;
});

console.log(collection); //returns [2,3]
console.log(array); //returns [1]

2. Using a reverse for loop

let array = [1,2,3];
let collection = [];

for(var i=array.length - 1; i>=0; i--) {
  if(array[i]>=2) {
     collection.push(array[i]);
     array.splice(i,1);
  }
}

console.log(collection); //returns [2,3]
console.log(array); //returns [1] 

The filter() function in this case requires an additional step to temporarily hold the elements that do not meet the condition. The reverse for loop offers a more cleaner way to achieve the same.

Assassinate answered 2/9, 2019 at 9:13 Comment(3)
i was also thinking about this case.Luci
You didn't say anything about needing to collect the entries you removed (and your second solution doesn't).Saltworks
@T.J.Crowder I've updated my answer. Also, my focus was more on the operation of the for...of loop. My bad, though, shoulda mentioned that earlier!!Assassinate
L
2

iterator skips the next entry on removing current entry in the array. So you should this way to achieve your desired result.

let array = [1,2,3];
let array_new = [1,2,3];
for(let entry of array) {
if(entry>=2) {
    let index = array_new.indexOf(entry);
    array_new.splice(index, 1);
}
} 
console.log(array_new);
//returns [1]
Luci answered 2/9, 2019 at 9:6 Comment(2)
In this case a reverse for loop offers a much more cleaner alternative :). See this answerAssassinate
@JuneM3ta yes it isLuci

© 2022 - 2024 — McMap. All rights reserved.