Home > Net >  Node.js for loop not leading to splice of all matching items
Node.js for loop not leading to splice of all matching items

Time:10-30

I'm trying to splice all matching items of parcel in an array inside an object.

Example of array of objects called documents:

[
{name: "first", content: ["1", "2", "1", "3"]},
{name: "second", content: ["2", "1", "1", "1"]},
{name: "third", content: ["4", "1", "3", "2"]},
]

parcel = 1 and I'm trying to splice all instances of 1.

Here's what I have at the moment:

console.log(parcel);
const parcel = 1;
console.log("doc start", documents);
for (i of documents) {
    // get index by count
    let count = -1;
    for (j of i.content) {
      count = count   1;
      console.log(j);
      if (j == parcel) {
        i.content.splice(count, 1);
      }
    }
  }
console.log("doc end", documents);

Here's what the console logs with different array example:

parcel 1
doc start [
  {
    title: '1',
    content: [
      '1', '2', '2',
      '3', '1', '1',
      '2', '3'
    ]
  }
]
1
2
3
1
2
3
doc end [ { title: '1', content: [ '2', '2', '3', '1', '2', '3' ] } ]

You can see that j doesn't seem to be looping through the whole of i.content, which means that the if statement is not running to splice all the correct items.

The end result of the console log should be:

doc start [{title: '1', content: [ '2', '2', '3', '2', '3' ]}]

I'm not sure why this is happening...

CodePudding user response:

Here's how map and filter might work. For each object in the iteration a new object is created using the spread syntax, and a new content array is created by filtering out all the 1s from the existing array.

const data=[{name:"first",content:["1","2","1","3"]},{name:"second",content:["2","1","1","1"]},{name:"third",content:["4","1","3","2"]}];

const out = data.map(obj => {
  return {
    ...obj,
    content: obj.content.filter(n => Number(n) !== 1)
  };
});

console.log(out);

CodePudding user response:

As you only want to filter by content you should use map and filter built in methods of arrays

const parcel = 1

const result = [
    {name: "first", content: ["1", "2", "1", "3"]},
    {name: "second", content: ["2", "1", "1", "1"]},
    {name: "third", content: ["4", "1", "3", "2"]},
    ].map(item =>({...item, content:item.content.filter(amount => Number(amount) !== parcel)}))

CodePudding user response:

If you want to delete multiple items from an Array you are iterating through you may get unexpected results, due to the way how for-in/for-of or array.forEach() works.

They all iterate through the array similar to a for(;;)-loop, where you just increase the index, no matter what.

They are all similar to:

for (let idx = 0; idx < array.length; idx  ) {
  // logic goes here
}

But if you delete the item at index 1 by using array.splice(), the item which was previously at index 2 becomes the item at index 1, all following items also reduce their index by one. Therefor the item which was previous index 2, will be skipped.

For more informations you can take a look at the spec of the for-of statements.

In this example, you can see, that the item at index 3 (value 4) is skipped.

const arr = [1,2,3,4,5,6];

let idx = 0;
for (const item of arr) {
    console.log(idx, item);
    if (item == 3) {
        arr.splice(idx,1);
    }
    idx  ;
}
console.log(arr);

If you want to mutate the array, there are several ways which can be used.

  • Array.prototype.reduceRight(callback, initialValue)
  • a for/while loop
    • that only increases the index if nothing was changed
    • reduces the index starting from the last one
  • Use Object.keys(obj).reverse() or Object.entries(obj).reverse()
    Object.entries(array).reverse().forEach(([idx, item]) => {
      /* splice logic goes here */
    })
    

Note: Array.prototype.filter should only be used, if the reference to the array isn't stored anywhere else.

Examples:

Using a while or for loop, that only increases the index if nothing was deleted.

const arr = [1,2,3,4,5,6];

for (let idx = 0; idx < arr.length;) {
  let item = arr[idx];
  console.log(idx, item);
  if (item == 3) {
     arr.splice(idx, 1);
  } else {
     idx  ;
  }
}

console.log(arr);

Going backwards through the index numbers of the array.

const arr = [1,2,3,4,5,6];

for (let idx = arr.length - 1; idx > -1; --idx) {
  let item = arr[idx];
  console.log(idx, item);
  if (item == 3) {
    arr.splice(idx, 1);
  }
}

console.log(arr);

With array.reduceRight(), which also starts at the highest item/index:

From the Docs:

initialValue

Value to use as accumulator to the first call of the callbackFn. If no initial value is supplied, the last element in the array will be used and skipped. Calling reduce or reduceRight on an empty array without an initial value creates a TypeError.

const arr = [1,2,3,4,5,6];
// only if the array is not empty
// acc or return value is not needed
arr.length && arr.reduceRight((acc, item, idx) => {
    console.log(idx, item);
    if (item == 3) {
        arr.splice(idx, 1);
    }
    // can be anything, but must be present
}, "unused Initial Value");
console.log(arr);

Based on your example using Object.entries():

const array = [
  { name: "first", content: ["1", "2", "1", "3"] },
  { name: "second", content: ["2", "1", "1", "1"] },
  { name: "third", content: ["4", "1", "3", "2"] },
];

for (const { content } of array) {
  if (Array.isArray(content)) {
    Object.entries(content).reverse().forEach(([idx, item]) => {
      if (item === "1") {
        content.splice(idx, 1);
      }
    })
  }
}

console.log(array);

  • Related