TopMarx
TopMarx

Reputation: 135

Replace Rows in Array using Google Apps Script

I'm still getting my head around Apps Script and just need some help editing an array.

So I have two arrays:

var arrayBig = SpreadsheetApp.getSheetByName('Big').getDataRange().getValues();

var arraySmall = SpreadsheetApp.getSheetByName('Small').getDataRange().getValues();

And I want to replace specific rows in arrayBig based on values in arraySmall, and then write that array back to my spreadsheet (the last part I'm ok with).

The two arrays have the same number of columns, but arraySmall has fewer rows.

The rows to replace:

So if the value in Column 1 = 3 replace row number 3 in arrayBig with the contents of the row from arraySmall where Column 1 = 3.

I think the answer has to do with the map method but I don't understand it. Just some pointers to get me started would be much appreciated, thank you.

Upvotes: 1

Views: 1609

Answers (2)

Yuri Khristich
Yuri Khristich

Reputation: 14537

Just in case. Another solution:

var arrayBig    = [ [1,"A","B"], [2,"C","D"], [3,"E","F"], [4,"G","H"] ];
var arraySmall  = [ [1,"P","Q"], [3,"Y","Z"] ];

var obj = {}; // it will be { "1":[1,"P","Q"], "3": [3,"Y","Z"] }

arraySmall.forEach( x => obj[x[0]] = x );

arrayBig = arrayBig.map( x => obj[x[0]] || x );

console.log(arrayBig); // [[1,"P","Q"], [2,"C","D"], [3,"Y","Z"], [4,"G","H"]];

It converts the small array into an object where first element of every row is a key and the row is its value. Then it loops through the big array and tries to get values of the object by keys (a first cell). If the key exists the row will replace, if key doesn't exist the for wont change.

Sequence of the rows in the small array doesn't matter as well.

Updage

You can use an array instead of an object if you want:

var arrayBig = [ [1,"A","B"], [2,"C","D"], [3,"E","F"], [4,"G","H"] ];
var arraySmall  = [ [1,"P","Q"], [3,"Y","Z"] ];

var arr = [];

arraySmall.forEach( x => arr[x[0]-1] = x ); // [ [1,"P","Q"], [], [3,"Y","Z"], [] ];

arrayBig = arrayBig.map( (x,i) => arr[i] || x);

console.log(arrayBig);

I think it will take more memory, but it works. But you need to be sure that first cells contain numbers only, since they're using as indexes of the array (indexes of array should be numbers, unlike keys that can be anything). And rows of first array should be sorted and has no skipped numbers (1, 2, 3, 4, etc). So, it's a more fragile solution. Just for educational purposes.

Upvotes: 1

andrewJames
andrewJames

Reputation: 22012

Here is one "get you started" approach:

You describe your data, but you do not provide any concrete samples - so here is my starting data, making some assumptions based on the information in the question:

var arrayBig = [ [1, "A", "B"], [2, "C", "D"], [3, "E", "F"], [4, "G", "H"] ];
var arraySmall  = [ [1, "P", "Q"], [3, "Y", "Z"] ];

With the above data, the expected outcome is the following, where rows 1 and 3 are replaced:

[ [1, "P", "Q"], [2, "C", "D"], [3, "Y", "Z"], [4, "G", "H"] ];

Here is the approach:

var arrayBig = [ [1, "A", "B"], [2, "C", "D"], [3, "E", "F"], [4, "G", "H"] ];
var arraySmall  = [ [1, "P", "Q"], [3, "Y", "Z"] ];

var mapper = new Map();
arraySmall.forEach((row) => { 
  mapper.set(row[0], row);
} );

newArrayBig = [];
arrayBig.forEach((row) => { 
  if ( mapper.has( row[0] ) ) {
    newArrayBig.push( mapper.get( row[0] ) );
  } else {
    newArrayBig.push( row );
  }
} );

console.log( newArrayBig );

This assumes you have an iron-clad guarantee that there are never more rows of data in arraySmall than there are in arrayBig. You can (and should) add some logic to test for that, for more robust code.

Notes

The Map object (not the map() method) is central to this approach. This object provides a lookup structure: an index value, pointing to a data value:

var mapper = new Map();
arraySmall.forEach((row) => { 
  mapper.set(row[0], row);
} );

In our case, the index is the number in the first cell of each arraySmall row. And the value it points to is the complete data array for that row, for example:

1 -> [1, "P", "Q"]
3 -> [3, "Y", "Z"]

We can now use this lookup data as we iterate through each row of our arrayBig data:

arrayBig.forEach((row) => { ... } );

The logic inside this forEach iterator basically says: If the map contains an array using the same number as the current arrayBig row, then use the arraysmall data. Otherwise, use the arrayBig data.

The newArrayBig.push() method adds that array to our results array.

You can then write this back to your spreadsheet, as needed.

Upvotes: 1

Related Questions