-1

I'd ask a better question, but I don't don't know how. Thanks for your help.

***ISSUE: I'm sending array vari to a function and it's coming back changed even though I didn't return it or even use same variable name. Desired function: variable vari does not change

I've logged the function and isolated the change to the [].forEach() statement below, noted with ***. I send vari but return varis and assign to new variable sum. How does this change the vari variable?

//this is what calls the sub function using vari, an array, 411.0, 13.0
  var sum = doSum1(vari);

    function doSum1(vari0) {
  // called from doSum1

  // grab Variance data // ALL COLUMNS FOR NOW // fix loc/stat columns below
  var vstat = vari0[0].indexOf('Status');
  vari1 = vari0.filter(r=>r[vstat]); // to ensure indexOf works, and speed processing
  var vhdr = ['Campaign ID','Campaign','Site ID','Site','Placement','Placement ID','Date','DCM Imp','Upw Imp','Tag Location','Status','Site Count','Site Imp'];
  // move loc and status over in place of variance and percent (loc/stat will be site ct/imp)
  varis=[];

  // *** THIS FOREACH CHANGES varis AND vari. Not sure how... see more specifically below
  ['Not Tracking','Undertracking','Overtracking','Absent in DCM'].forEach(rf=>{
    varis.push(vhdr.map(r=>''));
    varis[varis.length-1][0]=rf;
    varis.push(vhdr);
    if(vari1.filter(r=>r[vstat].indexOf(rf)>=0).length==0) {
      varis.push(vhdr.map(r=>''));
      varis[varis.length-1][0]='none found';    
    } else {
      varis.push(vari1.filter(r=>r[vstat].toString().indexOf(rf)>=0)[0]); // break out of outer []
      //fix loc/stat location
      //*** MORE SPECIFICALLY, this line in particular changes in vari, not just varis as intended.
      varis[varis.length-1].splice(9,4,varis[varis.length-1][11],varis[varis.length-1][12],'','')
    }
    varis.push(vhdr.map(r=>'')); // trailing blank line
  });
return varis;

I tried this in place of the splice as well, but same result... just not sure how varis is changing vari...

  varis[varis.length-1][9] = varis[varis.length-1][11];
  varis[varis.length-1][10] = varis[varis.length-1][12];
  varis[varis.length-1][11] = '';
  varis[varis.length-1][12] = '';
Jason Torpy
  • 124
  • 14
  • Objects (which includes arrays) are *mutable* values. I.e. anything that has a reference to it can change it. – Felix Kling May 22 '21 at 17:17
  • 1
    Instead of sending the object send a copy. – Cooper May 22 '21 at 17:25
  • 1
    Can you show show the function `llog()`. I see no another places where `vari0` or `vari0` could be changed – Yuri Khristich May 22 '21 at 18:06
  • 1
    And are these `vari1` and `varis` global variables? I'd try to add `var` in the lines where they are defined. Just to be on the safe side. – Yuri Khristich May 22 '21 at 18:13
  • thanks all. I should have a var definition there. I'll add that. I might send a copy as well. that's good info. anyone have any idea what here is specifically changing vari? – Jason Torpy May 22 '21 at 20:40
  • no luck reassigning the variable before sending it or declaring each of the variables. Any other ideas? – Jason Torpy May 22 '21 at 23:26
  • Can you give an example of what the original `vari` looks like? What is its original state and what is its changed state? – iansedano May 24 '21 at 07:46
  • good question. vari is an array, 411.0 rows (may change) 13.0 cols (could but normally doesn't change). draws from a good sheet with string/number/date values. --- the 'more specifically' comment shows the .splice(9,4...) that is intended to change varies by moving the 11th and 12th index back to 9th and 10th index. The problem is it changes vari. So vari has a bunch of rows that have the 11th and 12th index shifted back to the 9th and 10th spot. That's right for the new array varis but not for the old array vari. – Jason Torpy May 24 '21 at 17:43
  • I have tested this code with some test data and `vari` is not changed. Its probably due to the test data though. Please provide a sample output of `vari` before calling the function to be able to test with. Or a test project with the sample data. – iansedano May 25 '21 at 08:34

1 Answers1

3

vari is a 2D array. That means that every element in vari is an array as well, and as such passed by reference and subject to mutation.

The Array.splice() method mutates its argument array. In the code, each varis[varis.length-1].splice() call modifies an array object that is copied from vari1 by reference, and therefore also vari0 whose elements are array objects that are copied to vari1 by reference. This is what causes vari to mutate.

To avoid the issue, use one these patterns:

var vari1 = vari0.map(row => row.slice()).filter(r => r[vstat]);

or

var vari1 = vari0.map(row => row.map(value => value)).filter(r => r[vstat]);

The patterns use Array.map() and Array.slice()to get a shallow copy of the 2D array referenced by vari0 (i.e., vari).

The first map() creates a new array of that contains the rows of vari0. The rows are arrays and therefore mutable, so a slice() or another map() is required to copy the rows into new arrays as well.

Note that the copy is shallow, which means that only primitive values such as text strings and numbers are copied by value. Your comments indicate that the rows of vari only contain primitives, so the pattern will make a copy that is safe to modify and will not mutate vari. Were the rows of the vari 2D array contain yet more arrays or other objects, the would be copied by reference and therefore still be subject to mutation.

Note that Array.splice() and Array.slice() are very different from each other. The Array.splice() method mutates its argument array. The Array.slice() method creates a shallow copy of the array, and is in fact often used to safely copy 1D arrays that contain primitives. In your use case, the vari array does not contain primitives but arrays, so we need to call slice() within map() to copy the primitive values in the second level of the 2D array.

In the general case, deep cloning an array or another object is surprisingly complex. The patterns above are probably the simplest way to do it in your use case. See What is the most efficient way to deep clone an object in JavaScript?

doubleunary
  • 13,842
  • 3
  • 18
  • 51
  • the issue arose before I added the log function. I should note that the llog function is just Logger.log. It doesn't do anything. I put it in there to find the error. I just don't like typing 'Logger.log'. If it did do anthing, it would just do something to [248], right? Anyway, while I agree vari is potentially subject to mutation, I don't know where that might have happened. – Jason Torpy May 24 '21 at 21:10
  • `vari0[248]` is an array object and can be mutated during a function call, but that is not where the issue comes from. Edited the answer. – doubleunary May 24 '21 at 21:43
  • I thought it might be something like that. I did try const in a few locations. I recreated your specific suggestion (not sure why the splice() is needed). No luck though. the bug persists. – Jason Torpy May 24 '21 at 21:58
  • I'm not entirely familiar with const vs var. I read it, but it seemed basically the same. could I do const for varis (not vari)? if think not since I'm changing the value and returning it... would it help? – Jason Torpy May 24 '21 at 21:59
  • 1
    The point in the answer is _not_ `const` vs. `var` but the use of `map-map` or `slice()` to create a copy of the array. You can use `var` instead of `const` if you like. Edited the answer. – doubleunary May 25 '21 at 07:12
  • oh eureka. seems to work. thanks so much. Is this the easiest way to make that conversion? seems like a too much effort to just make a copy... – Jason Torpy May 25 '21 at 13:45
  • thought you got the bounty when I picked the answer. just awarded it now. – Jason Torpy May 29 '21 at 21:33