**Google Apps Script: V8 engine, map, filter, reduce, includes, 2d arrays, matrix**

Have you ever wanted to delete or add columns in a Google Sheet, based on another set of Sheet data?

I know I have.

There have been a number of instances where I wanted to insert new columns or removed unused columns in large Google Sheets projects with Google Apps Script.

In the past, I have dynamically set headers based on something like an IMPORTRANGE or FILTER, or simply from an array (`{}`

) from another sheet tab or Google Sheet. However, when I update the original data the headers change **but** all the data underneath them does not move along to the updated column with it.

Now all my data is not lined up with the header! As you can imagine, this creates some serious problems.

In this tutorial, we’ll show you how to use Google Apps Script to update your headers based on another sheets values. These sheets values can come from the current Google Sheet workbook or another one. We will also ensure that the data below the headers is migrated along with the new header location.

Take a look at the visual example below:

*As always, read as much or as little as you need to get the job done or learn the skill. *

## The Code

The following code contains an example. All the example data is drawn in from the `runsies()`

function. The main function to add and remove columns is the `addRemoveColByRowWithArray_()`

function. You see this being executed in the `runsies()`

function along with a bunch of arguments.

You should substitute the `runsies()`

function for your own function for your project and input in all the relevant arguments before calling the `addRemoveColByRowWithArray_()`

for this project.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
//An example function where you can call the addRemoveColByRowWithArray_ function. function runsies(){ // #### GLOBALS #### const SEARCH_SS = SpreadsheetApp.openById("1_S8oFiDI_DXME--O7ZDiZ2WcSAV2pmFrH5BH1ebWST8"); const UPDATE_SS = SEARCH_SS; //You can change this to the code above with whatever destination Google Sheet you want. const SEARCH_SHEET = SEARCH_SS.getSheetByName("keys"); const UPDATE_SHEET = SEARCH_SS.getSheetByName("data"); const SEARCH_RANGE = SEARCH_SHEET.getRange("A2:B11"); const UPDATE_RANGE = UPDATE_SHEET.getRange("B1:J5"); const SEARCH_MATRIX = SEARCH_RANGE.getValues(); const UPDATE_MATRIX = UPDATE_RANGE.getValues(); const SEARCH_COL_ROW = 1; //These are the number of rows or cols from the data collected. const UPDATE_ROW = 1; // ################ const new_matrix = addRemoveColByRowWithArray_(UPDATE_MATRIX, UPDATE_ROW, SEARCH_MATRIX, SEARCH_COL_ROW, false); // ################ // Add the updated data back into the sheet. const rowStart = UPDATE_RANGE.getRow(); const colStart = UPDATE_RANGE.getColumn(); const rowLen = new_matrix.length; const colLen = new_matrix[0].length; var newRange = UPDATE_SHEET.getRange(rowStart, colStart, rowLen, colLen); newRange.setValues(new_matrix); }; /*##################################################################### * Add and Remove Columns By Target Row Reference With Array * * @param {array} matrix : 2d array of data to be checked. * @param {number} rowRef : The row reference to match in the 'matrix' array. * @param {array} searchMatrix : 2d array of data to be used to update 'matrix'. * @param {number} searchColOrRow : The row or column reference to match in the 'searchMatrix'. * @param {boolean} sameFormat : whether the searchMatrix is the same as the 'matrix' if it is, true * otherwise it needs to be tranposed so mark it false. * @returns {array} Updated 'matrix' with removed or added items from 'searchMatrix'. Add or * removes entire columns. * */ function addRemoveColByRowWithArray_(matrix, rowRef, searchMatrix, searchColOrRow, sameFormat = true){ const newSearchMatrix = formatSearchArray_(matrix, searchMatrix, sameFormat); const searchArray = newSearchMatrix[searchColOrRow - 1]; const arry = matrix[rowRef - 1]; //Removes any column not in the searchArray. let matrixFiltered = matrix.map(row => { return row = row.filter((item, index) => { if(searchArray.includes(arry[index])) return true; }) }); //Adds any column from the searchArray not in the Matrix. // ## NOTE! This approach requires some sembelance of order between // ## the matrix and the searchMatrix reference rows. searchArray.map((val, index) => { if(val != matrixFiltered[rowRef - 1][index]){ matrixFiltered.map((row, r_index) => { row.splice(index,0,newSearchMatrix[r_index][index])}) }; }); return matrixFiltered; }; // ######## HELPER FUNCTIONS ######## /*##################################################################### * Creates a new array with selecte fill item. * * @param {number} len : Length of array. * @param {number|string} fillItem : Number or string to fill the array item. * @returns {array} The array the required length with required fill. * */ function newArrayFill(len, fillItem){ var arizzle = []; for(var i = 0; i < len; i++){arizzle[i] = fillItem}; return arizzle; }; /*##################################################################### * Transposes a matrix clockwise * https://stackoverflow.com/questions/17428587/transposing-a-2d-array-in-javascript * * @param {array} matrix : 2d array of data to be rotated 90 degrees clockwise. * @returns {array} Rotated array. * */ function transpose_(matrix){ return matrix.reduce((prevRow, nextRow) => nextRow.map((item, col) => (prevRow[col] || []).concat(nextRow[col]) ),[]); }; /*##################################################################### * Format SearchMatrix * * Rotates the search matrix if requested. Then, add extra row equal to the length of the 'matrix' * that is being edited. * * * @param {array} matrix : 2d array from which we derive row length * @param {array} searchMatrix : 2d array to be transformed * @param {boolean} sameFormat : if the 'matrix' and 'searchMatrix' are the same, then TRUE * @returns {array} Rotates serchMatrix if needed then adds any extra rows to match 'matrix' * */ function formatSearchArray_(matrix, searchMatrix, sameFormat){ let ary = sameFormat ? searchMatrix : transpose_(searchMatrix); if(ary.length < matrix.length){ let dif = matrix.length - ary.length; let blankRowArray = newArrayFill(ary[0].length,""); while(dif !== 0){ ary = ary.concat([blankRowArray]) dif--; }; }; return ary; }; |

#### Quick use guide

To run in your own project, copy and paste all the functions starting from `addRemoveColByRowWithArray_()`

all the way down to the bottom of the script above.

Then, paste the function call, `addRemoveColByRowWithArray_(),`

into your own project just like in the `runsies()`

example.

`addRemoveColByRowWithArray_()`

takes five arguments:

**Matrix**(*2d array*): the 2d array of values that needs changing.**Row Reference**(*number*): this is the row in the**Matrix**that will be referenced against the**search matrix**. This will probably be the header of your current data row you want to change. Note that the number is based on the first row you selected. For example, if your selected range is from C3:E19 and your header is in row 3 then that will be the first row in your selected range and you would make the row reference 1.**Search Matrix**(*2d array*): the 2d array of values that you will use to update your**Matrix**array.**Search Column or Row**(*number*): If your search matrix has headers running across the row or across the column (more on this next) select which column or row you will be using to compare to the**Matrix**to update it.**Same Format**(*boolean*)(optional): This last optional arguments, allows you to transpose your**Search Matrix**if the search matrix has its header reference running down a column instead of across a row like the**Matrix**. If the Search Matrix has headers running across the row then you can either select true or leave it empty. Otherwise, you can mark it false if your headers run down the column.

The `addRemoveColByRowWithArray_()`

will return a matrix of updated values for you to set back into your Google Sheet.

#### Some limitations

This function is pretty handy but there are some limitations that are outside the scope of this simple tutorial.

First, you will need to have a relatively similar **Matrix** and **Search Matrix** that is roughly sorted in the same order. This is because when new columns are added from the Search Matrix it will reference the previous matching headers between the two matrices. If the two headers are too far out of order, then it will not do a good job adding in the new columns.

Secondly, this approach only takes in values and returns values. If you have formulas in your Matrix, they will be removed. Having said that, you can always run this function on regions that don’t have formulas in them.

That’s about all you will need to get started quickly. If you are interested in seeing an example or getting under the hood, read on!

## An Example

Let’s say I have a jibberish Google Sheet with rows containing a category item of three-letter words:

- A short id for these words
- A number assignment row
- Some randomly assigned greeting words that have not yet been completely filled out
- Some overly enthusiastic “go” words in each of the row’s cells.

Something like this:

Essentially, a list of category items in each column with a set of related data underneath each item.

We’ll call this Google Sheet tab our ** ‘Data’** sheet.

Occasionally we get updates to our list of categories and we need to update this sheet by adding in new categories and deleting some redundant category columns.

This updated data might be displayed in the same format with categories running horizontally across a single row or the categories might be listed vertically:

We’ll call this data we want to search, our ** ‘Key’ **Sheet. This data could come from any other Google Sheet workbook, but for this example, we will put it in the same workbook.

Let’s use the category items to compare the two sheets. If there is a category item not in the * ‘key’* sheet we will remove it and its entire column from the

*sheet. Likewise, if there is a category item in the*

**‘data’***sheet but not in the*

**‘key’***sheet then we will insert that in the correct location in the*

**‘data’***sheet along with the id. Users can later fill out the rest of the information.*

**‘data’**Let’s move over to the Google Apps Script editor and create a `runsies()`

function to collect our data, run it through the add and remove process and update the * ‘data’* sheet.

Looking to learn more about Google Apps Scripts in a more structured format? **Udemy** has some great courses that can get you from the basics to a real **Google Apps Script pro**!

Got a more specific problem you need help with, but don’t have the time to develop the skills? **Fiverr**’s your best bet to find a **skilled Google Apps Script professional** to solve your problem ** quickly** and

**. ***

*cheaply***The above affiliate links have been carefully researched to get you to what you specifically need. If you decide to click on one of these links it will cost you just the same as going to the site. If you decide to sign up, I just get a little pocket money to help pay for the costs of running this website. *

### runsies()

Take a look at the code. Note, I have made it overly explicit for the benefit of multiple levels of learners.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
//An example function where you can call the addRemoveColByRowWithArray_ function. function runsies(){ // #### GLOBALS #### const SEARCH_SS = SpreadsheetApp.openById("1_S8oFiDI_DXME--O7ZDiZ2WcSAV2pmFrH5BH1ebWST8"); const UPDATE_SS = SEARCH_SS; //You can change this to the code above with whatever destination Google Sheet you want. const SEARCH_SHEET = SEARCH_SS.getSheetByName("keys"); const UPDATE_SHEET = SEARCH_SS.getSheetByName("data"); const SEARCH_RANGE = SEARCH_SHEET.getRange("A2:B11"); const UPDATE_RANGE = UPDATE_SHEET.getRange("B1:J5"); const SEARCH_MATRIX = SEARCH_RANGE.getValues(); const UPDATE_MATRIX = UPDATE_RANGE.getValues(); const SEARCH_COL_ROW = 1; //These are the number of rows or cols from the data collected. const UPDATE_ROW = 1; // ################ const new_matrix = addRemoveColByRowWithArray_(UPDATE_MATRIX, UPDATE_ROW, SEARCH_MATRIX, SEARCH_COL_ROW, false); // ################ // Add the updated data back into the sheet. const rowStart = UPDATE_RANGE.getRow(); const colStart = UPDATE_RANGE.getColumn(); const rowLen = new_matrix.length; const colLen = new_matrix[0].length; var newRange = UPDATE_SHEET.getRange(rowStart, colStart, rowLen, colLen); newRange.setValues(new_matrix); }; |

#### Globals

From row 4 to 20 we set up all the variables we need for this task.

Any variable with the suffix * SEARCH *is the one with the new list of headers. Variables that contain

**UPDATE****reference to the original data that will be updated.**

Lines 5 and 6 calls up the two spreadsheets. We do this by using Google Apps Scripts SpreadsheetApp Class. Where possible I generally prefer to grab a spreadsheet by its ID.

We do this by using the openById(*paste ID here*) method. If both your SEARCH and UPDATE data are on the same Google Sheet workbook, then you can simply reference SEARCH_SS when creating UPDATE_SS. Just like I have done.

Next, we use getSheetByName(*Sheet tab name here*) to locate the correct Google Sheet tab you are on *(Lines 8 & 9).* In this example, my SEARCH matrix is in the * Keys *sheet tab and my UPDATE matrix is in the

*sheet tab.*

**Data**Now we need to get the range of both of our SEARCH and UPDATE matrices with the getRange() method (*Lines 11 & 12*). The getRange method can take a numerical range or a range based on A1 notation. I’ve chosen the latter here.

The final global variables we need is to set which ether column or row we will be using as our search column or row.

Let’s say our SEARCH data in our * Key *sheet runs vertically, we would then select the column. For us, the column we want to compare is column A. The first column in our range, so we would set our

`SEARCH_COL_ROW`

to 1.For our Data or UPDATE range, it will always be displayed with headers running across the to of the sheet. We will set our UPDATE row to 1.

#### Run addRemoveColByRowWithArray_()

Once you have all your Global variables in place, it’s time to run the code. Scroll up to the Quick Use Guide for a breakdown, but it should make sense now that you have an understanding of the variables.

1 |
const new_matrix = addRemoveColByRowWithArray_(UPDATE_MATRIX, UPDATE_ROW, SEARCH_MATRIX, SEARCH_COL_ROW, false); |

`addRemoveColByRowWithArray_()`

will return the values of the updated matrix in the `new_matrix`

variable.

#### Returning the Updated Matrix back to the sheet

In the final stage of the` runsies()`

function, we need to return the updated data back to the sheet.

Unfortunately, we cannot use the old set of `getRange()`

parameters from our original `UPDATE_RANGE`

variable because the column range is likely to have increased or decreased in size. Instead, we will get the start row and column of the range using the getRow() and getColumn() Google Apps Script methods respectively *(Lines 28 & 29).*

Then we can get the row length and column length of our `new_matrix`

*(Lines 30 & 31)*.

Remember, I mentioned earlier, that the `getRange()`

method can take either a numerical value or A1 notation? Well, this time we will use the numerical value arguments. The `getRange()`

method in this format takes values as follows:

1 |
<em>sheetLocation</em>.getRange(<em>start row, start column, row length, column length</em>) |

With our new range, we now simply use setValues() to display the updated matrix on our Google Sheet *(Line 3).*

### addRemoveColByRowWithArray_()

This is where all the magic happens. In our example, this is called from the `runsies()`

function. You should put it anywhere in your project where you will use it.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/*##################################################################### * Add and Remove Columns By Target Row Reference With Array * * @param {array} matrix : 2d array of data to be checked. * @param {number} rowRef : The row reference to match in the 'matrix' array. * @param {array} searchMatrix : 2d array of data to be used to update 'matrix'. * @param {number} searchColOrRow : The row or column reference to match in the 'searchMatrix'. * @param {boolean} sameFormat : whether the searchMatrix is the same as the 'matrix' if it is, true * otherwise it needs to be tranposed so mark it false. * @returns {array} Updated 'matrix' with removed or added items from 'searchMatrix'. Add or * removes entire columns. * */ function addRemoveColByRowWithArray_(matrix, rowRef, searchMatrix, searchColOrRow, sameFormat = true){ const newSearchMatrix = formatSearchArray_(matrix, searchMatrix, sameFormat); const searchArray = newSearchMatrix[searchColOrRow - 1]; const arry = matrix[rowRef - 1]; //Removes any column not in the searchArray. let matrixFiltered = matrix.map(row => { return row = row.filter((item, index) => { if(searchArray.includes(arry[index])) return true; }) }); //Adds any column from the searchArray not in the Matrix. // ## NOTE! This approach requires some sembelance of order between // ## the matrix and the searchMatrix reference rows. searchArray.map((val, index) => { if(val != matrixFiltered[rowRef - 1][index]){ matrixFiltered.map((row, r_index) => { row.splice(index,0,newSearchMatrix[r_index][index])}) }; }); return matrixFiltered; }; |

#### Parameters

The `addRemoveColByRowWithArray_()`

function uses 5 parameters:

: This is the 2d array of data we need to check. In the example, this will come from`matrix`

(2d array)`UPDATE_MATRIX`

in the**Data**: The`rowRef`

(number)sheet row we will be comparing against the**Data**sheet row or column reference. This comes from**Keys**`UPDATE_ROW`

in`runsies()`

.: This is the array we will use to update the Data`searchMatrix`

(2d arrray)`matrix`

. It is drawn from`SEARCH_MATRIX`

in thesheet tab.**Keys**The row or column to reference in the`searchColOrRow`

(number):`SEARCH_MATRIX`

.: If the`sameFormat`

(boolean)`matrix`

and`searchMatrix`

both have their references running across the Google Sheet in a row then this is*true*. However, if the`searchMatrix`

reference runs down a column, like in our example, then this is marked,*false*.

#### Setting up the constants

The first thing we need to do is check if the `searchMatrix `

is the same format as the `matrix`

. If the `searchMatrix`

runs down a column instead of across rows, then it will need to be transposed.

To do this we use a helper function created called `formatSearchArray_()`

. This takes 3 arguments:

`matrix`

`searchMatrix`

`sameFormat`

The`formatSearchArray_()`

will then transpose the `searchArray`

if `sameFormat`

is false. It will then fill out any extra rows of data with an empty text value in each cell so that it matches the number of rows of the `matrix`

2d array variable before returning it to the `newSearchMatrix`

variable.

We then use this `newSearchMatrix`

to find the `searchArray`

*(Line 16)*. We will use this as our reference row to compare against our matrix reference row.

On line 17, we grab the matrix array that we will reference in the `arry`

constant variable.

#### Remove any column not in the search array

Finally, we can get down to get our task done!

Lines 19 – 24, remove any column from the matrix that is not present in the `searchArray`

.

For example, our current `searchArray`

contains these values:

`[Cat, Fat, Sit, Bin, Bog, Bat, Zap, Zen, Kin, Boy]`

And, our `arry`

contains these values:

`[Cat, Fat, Bin, `

**Bah**, Bat, Zap, Kin, Boy, **Toc**]

We can see that the `searchArray`

does not contain, * Bah* and

*, so those columns need to be removed from the*

**Toc**`matrix`

.
1 2 3 4 5 6 |
//Removes any column no in the searchArray. let matrixFiltered = matrix.map(row => { return row = row.filter((item, index) => { if(searchArray.includes(arry[index])) return true; }) }); |

Javascript’s map and filter to the rescue here.

We first use **map** to iterate through each row of data in the `matrix`

. While in each row we use **filter** to get the index (location) of each item in the row.

Within **filter**, we want to check the `searchArray`

using the includes method to see if the current index of the `arry`

variable is included in the `searchArray`

. If it is, then that current rows item will be kept and returned in the `matrixFiltered`

variable.

This process moves through each item in the row of the `matrix`

. Once the row is complete, it moves onto the next row and completes the **filter** process again until all rows are done.

#### Add any column from the searchArray into the Matrix

In the next, stage of the function, we want to add any column to the `newSearchMatrix`

that does not appear in the `arry`

but is in the `searchArray`

.

To do this we are going to stretch the use of **map** (*abuse?*).

1 2 3 4 5 6 7 8 9 10 |
//Adds any column from the searchArray not in the Matrix. // ## NOTE! This approach requires some sembelance of order between // ## the matrix and the searchMatrix reference rows. searchArray.map((val, index) => { if(val != matrixFiltered[rowRef - 1][index]){ matrixFiltered.map((row, r_index) => { row.splice(index,0,newSearchMatrix[r_index][index])}) }; }); |

We are not actually going to return anything from **map**, which is the common practice. (*I can feel the anger bubbling in some readers, but, you know… hackity hack hack).*

All our changes will update the `matrixFiltered`

array we created earlier.

First, we map through the `searchArray`

. We will need to note both the value and the index of each item in the array. If the value in the `searchArray`

does not match the first row of the `matrixFiltered`

array at the same index, then we need to add that column.

To do that, we map through `matrixFiltered`

taking its row, and the index of that row *( r_index). *On the assigned row, we splice in the new column item:

`row.splice(index,0,newSearchMatrix[r_index][index])`

Splice takes 3 arguments:

**Start index of the array.**For us, this is the currently iterated index of the`searchArray`

.**The number of items to delete.**This will be zero. We are not deleting anything here.**The elements to add to the array.**This will be the value in the row and column of the`newSearchMatrix`

.

Finally, `matrixFiltered`

is returned back to the `runsies()`

function.

On to the helper functions!!!

### formatSearchArray_()

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/*##################################################################### * Format SearchMatrix * * Rotates the search matrix if requested. Then, add extra row equal to the length of the 'matrix' * that is being edited. * * * @param {array} matrix : 2d array from which we derive row length * @param {array} searchMatrix : 2d array to be transformed * @param {boolean} sameFormat : if the 'matrix' and 'searchMatrix' are the same, then TRUE * @returns {array} Rotates serchMatrix if needed then adds any extra rows to match 'matrix' * */ function formatSearchArray_(matrix, searchMatrix, sameFormat){ let ary = sameFormat ? searchMatrix : transpose_(searchMatrix); if(ary.length < matrix.length){ let dif = matrix.length - ary.length; let blankRowArray = newArrayFill(ary[0].length,""); while(dif !== 0){ ary = ary.concat([blankRowArray]) dif--; }; }; return ary; }; |

The `formatSearchArray_()`

function modifies the `searchMatrix`

by transposing it if the reference data is running down a column and then fills out the array with empty text (“”) to match the `matrix`

array’s length.

The `formatSearchArray_()`

uses 3 parameters:

This is simply used to get the row length we need to add to the updated version of the search array.`matrix`

(2d array):: This is the array we will use to update the matrix.`searchMatrix`

(2d array): If true, nothing changes, if false, then we transpose the`sameFormat`

(boolean)`searchMatrix`

to match the`matrix`

.

First, we check if the `searchMatrix`

needs to be transposed. This is done with a ternary operator *(Line 16).* It says that if `sameFormat`

is *true*, proceed with the `searchMatrix`

. However, if the `sameFormat`

is *false*, run the transpose_() helper function on `searchMatrix`

. This will be returned as the `ary`

variable.

Then we check the length of rows of the `ary`

variable and if it is less than the length of rows of the matrix, we need to add a few blank rows into the `searchMatrix`

*(Line 18).* Otherwise, when we move on to pasting the final result back into Google Sheets we are going to get a row mismatch error.

To add any extra rows, we must first find the difference in length between the two matrices *(Line 19). *Once we have that, we are going to make use of a little helper function we used in another project newArrayFill().

`newArrayFill()`

creates a blank array of *n *values. This function has two arguments. The length of the array, and what value you want to prefill the array with.

For us, we will want to add the length (number of columns) in the row and set the elements to an empty text (“”).

Next, we will use a while loop to add a row to `ary`

equal to the difference (`dif`

) in rows between the matrix and the `searchMatrix`

.

We then return the `ary`

to `addRemoveColByRowWithArray_()`

.

### transpose_()

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/*##################################################################### * Transposes a matrix clockwise * https://stackoverflow.com/questions/17428587/transposing-a-2d-array-in-javascript * * @param {array} matrix : 2d array of data to be rotated 90 degrees clockwise. * @returns {array} Rotated array. * */ function transpose_(matrix){ return matrix.reduce((prevRow, nextRow) => nextRow.map((item, col) => (prevRow[col] || []).concat(nextRow[col]) ),[]); }; |

The `transpose_()`

function is a pretty standard coding problem that is often used in job interviews from what I hear. There are a number of solutions, but this one is my favourite. You can check out more on this here.

This function takes a 2d array and rotates it clockwise and returns it.

We use the reduce method here. First, we set the `prevRow`

and `nextRow`

parameters. Don’t forget to add an empty array as an initial value at the end (*Line 13)* to be referenced first. Then, for every row we run **map .**

*If there is an element in a previous row’s current column index, then we use concat to join row at the same index as the*

`col`

.## Conclusion

This is a pretty fast way to update column base on a modified key set in Google Sheets using Google Apps Script.

Upon reflection, I almost wrapped all the helper functions into one method along with the primary `addRemoveColByRowWithArray_()`

. Though I am not always certain that is a good approach in smaller projects. Big projects, for sure; you don’t want a heap of function names floating around causing the greater potential for conflict.

Anyway, tell me what you think.

This project was a fun opportunity for me to try out Google Apps Script’s new V8 runtime and some of the juicy modern Javascript that comes with it. Please excuse me if I went a little overboard in my excitement.

Happy coding!

~Yagi