Home > front end >  Array of object arrays filtering does not retain the changes outside forEach
Array of object arrays filtering does not retain the changes outside forEach

Time:10-23

Use case: wrapping n object arrays in an array to loop over all arrays in one go and filter the objects on a property.

Known solutions I want to avoid:

[arr1,arr2,arr3] = [arr1,arr2,arr3].map(...)

["arr1","arr2","arr3"].forEach(arrName => window[arrName] = .....)

In the code below, why are the arrays not changed outside the forEach - some "pass by reference" issue? How to solve this?

Expected result:

[{"B":"b","remove":false},{"C":"c"}]
[{"A":"a","remove":false},{"C":"c","remove":false}]
[{"B":"b","remove":false}]

Actual result

[{"A":"a","remove":true},{"B":"b","remove":false},{"C":"c"}]
[{"A":"a","remove":false},{"B":"b","remove":true},{"C":"c","remove":false}]
[{"A":"a","remove":true},{"B":"b","remove":false},{"C":"c","remove":true}]

How to retain the changes outside the forEach?

let arr1 = [
{"A":"a", "remove":true},
{"B":"b", "remove":false},
{"C":"c"}
];
let arr2 = [
{"A":"a", "remove": false},
{"B":"b", "remove": true},
{"C":"c", "remove": false}
];
let arr3 = [
{"A":"a", "remove": true},
{"B":"b", "remove": false},
{"C":"c", "remove": true}
];


[arr1, arr2, arr3]
  .forEach(arr =>  {
    arr = [...arr.filter(({remove})=> !remove)]
    console.log(JSON.stringify(arr)); // works
  });
console.log("---------------");
// check it worked    
[arr1, arr2, arr3].forEach(arr => console.log(JSON.stringify(arr)));

CodePudding user response:

When you create an array using objects, the array elements themselves become references to the objects you used to instantiate the array:

let a1 = { a: 1 };
let a = [ a1 ];
console.log(a[0] == a1);  // Prints true

However, if you reassign a value to an element of that array, then that reference changes. The array element gets a new reference and breaks the link to the object used to first create the array:

let a1 = { a: 1 };
let a = [ a1 ];
console.log(a[0] == a1);  // Prints true
let a[0] = { a: 1 };
console.log(a[0] == a1);  // Prints false

The vice-versa is true, if you re-assign a value to the variable you used to instantiate the array, the array element is not affected:

let a1 = { a: 1 };
let a = [ a1 ];
console.log(a[0] == a1);  // Prints true
let a1 = { a: 1 };
console.log(a[0] == a1);  // Prints false

But if you simply change the property of the array element, then the references are unchanged:

let a1 = { a: 1 };
let a = [ a1 ];
console.log(a[0] == a1);  // Prints true
a1['b'] = 2;
console.log(a[0] == a1);  // Prints true
console.log(JSON.stringify(a[0]));  // Prints '{"a":1,"b":2}'

In fact, this is the behaviour for any object: a re-assignment changes its reference:

let a1 = { a: 1 };
let a2 = a2;
console.log(a1 == a2);  // Prints true
a2 = { a: 1 };
console.log(a1 == a1);  // Prints false

What you were doing in your forEach() was re-assigning new value. The filter() creates a copy of the array it filtered, so it is not the same object as the original.

You can modify the callback to your forEach() to modify the same object. One way I could think of is by using a for-loop from the end of each array and remove items from the array using splice():

let arr1 = [
  {"A":"a", "remove":true},
  {"B":"b", "remove":false},
  {"C":"c"}
];
let arr2 = [
  {"A":"a", "remove": false},
  {"B":"b", "remove": true},
  {"C":"c", "remove": false}
];
let arr3 = [
  {"A":"a", "remove": true},
  {"B":"b", "remove": false},
  {"C":"c", "remove": true}
];


[arr1, arr2, arr3].forEach((arr, index) =>  {
    for (i = arr.length - 1; i >= 0; i--) {
      if (arr[i].remove) {
        arr.splice(i, 1);
        i--;
      }
    }        
    console.log(JSON.stringify(arr)); // works
  });
console.log("---------------");
// check it worked    
[arr1, arr2, arr3].forEach(arr => console.log(JSON.stringify(arr)));

CodePudding user response:

forEach does not change nothing on the arrays, just iterates on them, I'll recomend to use a .map to assign the returned result to a variable

.map() doc

CodePudding user response:

Try to use map instead

const result = [arr1, arr2, arr3]
  .map(arr =>  {
    arr = [...arr.filter(({remove})=> !remove)]
    console.log(JSON.stringify(arr)); // works
    return arr;
  });

if you want to reassign the values

 [arr1, arr2, arr3] = [arr1, arr2, arr3]
  .map(arr =>  {
    arr = [...arr.filter(({remove})=> !remove)]
    console.log(JSON.stringify(arr)); // works
    return arr;
  });

Upd for the general array of arrays:

let t = [arr1, arr2, arr3];

  t.forEach((arr, key) =>  {
    t[key] = [...arr.filter(({remove})=> !remove)]
    console.log(JSON.stringify(arr)); // works
  });

Or if you want a general unsafe solution:

 let t = ['arr1', 'arr2', 'arr3'];
    
  t.forEach((arrName) =>  {
     window[arrName] = [...window[arrName].filter(({remove})=> !remove)]
     console.log(JSON.stringify(arr)); // works
  });

CodePudding user response:

Maybe this is not a "mainstream" answer, but if you really want to change the original arrays you could do the following:

let arr1 = [
{"A":"a", "remove":true},
{"B":"b", "remove":false},
{"C":"c"}
];
const arr2 = [
{"A":"a", "remove": false},
{"B":"b", "remove": true},
{"C":"c", "remove": false}
];
let arr3 = [
{"A":"a", "remove": true},
{"B":"b", "remove": false},
{"C":"c", "remove": true}
];

[arr1, arr2, arr3].forEach(arr=>{
    let i=0;
    arr.forEach((c,_,a)=> c.remove||(a[i  ]=c));
    arr.length = i;
  });
// check it worked    
[arr1, arr2, arr3].forEach(arr => console.log(JSON.stringify(arr)));

In this approach I replaced the .filter() by a .forEach() loop that reassigns individual elements of the processed array. And, as you can see, it also works on const arr2 (I changed it into a const for demonstration purposes), whereas a reassignment via a .map() approach would not.

  • Related