How do I add the values from three arrays? - google-apps-script

Is there an easy way to add the elements of arrays together? Let's say I have 3 arrays.
[15,22,35,40]
[10,20,12,20]
[11,24,14,22]
What I want to do is add up the totals so it would be:
[36,66,61,82]
and then overwrite [11,24,14,22] with those values.
Forgot to add the code I already have:
function combineItems(){
sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == "Product A") {
medData = sheet.getRange(i + 1, 7, 1, 4).getValues();
console.log(medData);
}
if (values[i][j] == "Product B") {
smData = sheet.getRange(i + 1, 7, 1, 4).getValues();
console.log(smData);
}
if (values[i][j] == "Product C") {
newData = sheet.getRange(i + 1, 7, 1, 4).getValues();
console.log(newData);
}
}
}
}
So I want to add up Product A, Product B, and Product C and then overwrite Product C with those new totals.

Solution:
function myFunction(data) {
return data[data.length-1].map((i, index) => i =
data.reduce((prev, curr) => prev + curr[index], 0)
)
}
Example:
const productA = [15,22,35,40]
const productB = [10,20,12,20]
const productC = [11,24,14,22]
const newData = myFunction([productA, productB, productC])
/**
Expected Output:
[36.0, 66.0, 61.0, 82.0]
**/
How It Works:
Taking the input of a multidimensional array, we alter the last row in place (.map()) by reducing (.reduce()) all values of the input data by index for each row.
(Reduce each column of the data into the last row.)
Suggested Reading:
Array.map()
Array.reduce()

Related

Why is this function not deleting many rows at once correctly?

This function works with a little bit of data, but not with hundreds of rows and I wonder if I'm missing some Spreadsheet.flush() or something of this nature.
const values = [["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"],["2022-12-31T06:00:00.000Z"]];
function DeleteRows(sheetName, year) {
sheetName = 'Saved Budgets'//For tests
year = '2022' //For tests
var SS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName); //Get Open Lines Sheet
var lastRow = SS.getLastRow();
var range = SS.getRange(2, 1, lastRow - 1, 1); //get range
range.sort({ column: 1, ascending: false }) // filter data descending
var firstRowToDelete = 0;
var numOfRows = 1; // starting row to be increment and become the number of rows
var values = range.getValues();//Got it for comparison
for (let a = 0; a < values.length; a++) {
let dt = new Date(values[a]).getFullYear();
if (dt == year) {
firstRowToDelete = parseInt(a);
numOfRows++
}
}
if (numOfRows != 1) {
numOfRows = numOfRows - 1 // minus 1 to get the last row
SS.deleteRows(firstRowToDelete, numOfRows);
}
range.sort({ column: 1, ascending: true }) // filter data again ascending
}
If you want to delete rows that the column "A" is year = '2022', firstRowToDelete = parseInt(a); is the last index of the rows that the column "A" is year = '2022'. And, numOfRows is the number of rows. In this case, I'm worried that all rows that the column "A" is year = '2022' cannot be deleted. And also, when the values are large, the rows for deleting might be over the bottom of the sheet, and/or range of range.sort({ column: 1, ascending: true }) might be over the bottom of the sheet. I thought that this might be the reason for your issue.
If you want to remove this issue, when your script is modified, how about the following modification?
From:
for (let a = 0; a < values.length; a++) {
let dt = new Date(values[a]).getFullYear();
if (dt == year) {
firstRowToDelete = parseInt(a);
numOfRows++
}
}
if (numOfRows != 1) {
numOfRows = numOfRows - 1 // minus 1 to get the last row
SS.deleteRows(firstRowToDelete, numOfRows);
}
range.sort({ column: 1, ascending: true }) // filter data again ascending
To:
for (let a = 0; a < values.length; a++) {
let dt = new Date(values[a]).getFullYear();
if (dt == year) {
if (firstRowToDelete == 0) firstRowToDelete = a + 2; // Modified
numOfRows++
}
}
if (numOfRows != 1) {
numOfRows = numOfRows - 1;
SS.deleteRows(firstRowToDelete, numOfRows);
}
SS.getRange(2, 1, SS.getLastRow() - 1, 1).sort({ column: 1, ascending: true }); // Modified
As another modification, how about the following modification?
From:
var values = range.getValues();//Got it for comparison
for (let a = 0; a < values.length; a++) {
let dt = new Date(values[a]).getFullYear();
if (dt == year) {
firstRowToDelete = parseInt(a);
numOfRows++
}
}
if (numOfRows != 1) {
numOfRows = numOfRows - 1 // minus 1 to get the last row
SS.deleteRows(firstRowToDelete, numOfRows);
}
range.sort({ column: 1, ascending: true }) // filter data again ascending
To:
var values = range.getDisplayValues();
var numOfRows = values.filter(([a]) => new Date(a).getFullYear() == year).length;
if (numOfRows > 0) {
var firstRowToDelete = values.findIndex(([a]) => new Date(a).getFullYear() == year);
SS.deleteRows(firstRowToDelete > -1 ? firstRowToDelete + 2 : firstRowToDelete, numOfRows);
}
SS.getRange(2, 1, SS.getLastRow() - 1, 1).sort({ column: 1, ascending: true });
Try this:
NOTE: This is all based on the assumption that the values global variable is actually a data in the spreadsheet, and you would want to remove all data with 2022.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); //you can change this to specify a specific sheet
var range = ss.getRange(2,1,ss.getLastRow(), ss.getLastColumn());
var values = range.getValues();
var year = /2022/; // change this to filter other years
var newval = values.filter(x=>year.test(x) ? null : x);
console.log(newval); //to check if it populates the correct data during logging.
range.clearContent(); //clears the data based on the current range keeping the formatting.
var newrange = ss.getRange(2,1,newval.length, ss.getLastColumn()); //creates a new range based on the size of `newval`
newrange.setValues(newval);
}
Explanation:
var range = ss.getRange(2,1,ss.getLastRow(), ss.getLastColumn()); gets the current data on the spreadsheet, including the columns.
Using var values = range.getValues(); we get a 2D array structure of the data on the spreadsheet.
Using filter() and test() method on var newval = values.filter(x=>year.test(x) ? null : x); using a ternary operator to test whether an array element contains the year to filter out.
range.clearContent(); to delete the contents of the range.
var newrange = ss.getRange(2,1,newval.length, ss.getLastColumn()); creates a new range based on the new array.
newrange.setValues(newval); sets the new value on the spreadsheet
Screenshots:
NOTE: Multiple columns in the data are for testing to see dynamic deletion even if there is additional data on the columns.
Initial data:
After running the script:
Execution duration:
References:
https://developers.google.com/apps-script/reference/spreadsheet/range#clearContent()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test

Merge cells having same values bottom down

I have a google sheet where i need to merge the cells if the values are repeating
The expected is to merge the cell from A1 to A4 and have a single value A in it.
I have tried with,
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getActiveRange();
//var values = range.getValues();
var numCols = values[0].length;
values = [
["A", "B", "C", "D", "E"],
["A", "B", "S", "D", "E"],
["A", "D", "C", "D", "E"],
["A", "B", "C", "D", "K"],
["c", "B", "W", "D", "K"],
["A", "B", "C", "D", "E"],
]
for (var j = 0; j < numCols; j++) {
var placer = '';
for (var i = 0; i < values.length - 1; i++) {
if (values[i][j] == values[i + 1][j])
range.getCell(i + 2, j + 1).setValue('');
}
}
You want to merge cells vertically when the cells have the same values to the vertical direction.
You want to achieve the following situation.
Input
Output
If my understanding is correct, how about this modification? Please think of this as just one of several answers.
The flow of my sample script is as follows.
Transpose the values.
Start and end addresses for merging cells are retrieved from the transposed values.
Merge cells using the retrieved addresses.
Sample script:
// Retrieve values from the active sheet.
var sheet = SpreadsheetApp.getActiveSheet();
var values = sheet.getDataRange().getValues();
// Transpose values.
var res1 = values[0].map(function(_, i) {return values.map(function(e) {return e[i]})});
// Merge cells.
res1.forEach(function(col, i) {
var temp = {};
col.forEach(function(row, j) {
if (row === col[j + 1] && !(row in temp)) {
temp[row] = j;
} else if (row != col[j + 1] && row in temp) {
sheet.getRange(temp[row] + 1, i + 1, (j - temp[row]) + 1, 1).merge();
temp = {};
}
});
});
Note:
This is a simple sample script. So please modify this for your situation.
References:
merge()
forEach()
If I misunderstood your question and this was not the result you want, I apologize.
Edit:
This is the answer for the additional question.
Usage:
Manually select cells "A2:C6" on "Sheet1".
Run the script.
Script:
// Retrieve values from the active sheet.
var range = SpreadsheetApp.getSelection().getActiveRange();
var sheet = range.getSheet();
var values = range.getValues();
// Transpose values.
var res1 = values[0].map(function(_, i) {return values.map(function(e) {return e[i]})});
// Offset
var r = range.getRow() - 1;
var c = range.getColumn() - 1;
// Merge cells.
res1.forEach(function(col, i) {
var temp = {};
col.forEach(function(row, j) {
if (row === col[j + 1] && !(row in temp) && row != "") {
temp[row] = j;
} else if (row != col[j + 1] && row in temp) {
sheet.getRange(r + temp[row] + 1, c + i + 1, (j - temp[row]) + 1, 1).merge();
temp = {};
}
});
});
Result:
Before:
After:

Copying or updating the row based on the occurrence of a row cell value

I'm trying to add a given row or update an existing row to a Google Sheet based on a specific value in the row data. My row data is represented as an array object, like this: [id, number, date, type, url, count].
What I expect is that, if there exists a row with a matching number, I increment the count by 1 in the same range in the Google Sheet, else I add a new row with my row data.
Here's what I've tried so far, but it works only for count=2 and not beyond that.
function copyRowBasedOnNumber(sheetId, sheetName, rowData) {
var sheet = SpreadsheetApp.openById(sheetId);
var sheetname = sheet.getSheetByName(sheetName);
var count = 0;
var matchingRow = sheetname.getLastRow();
var values = sheetname.getDataRange().getValues();
for (var row in values) {
for (var col in values[row]) {
if (values[row][col] == rowData[1]) { // rowData[1] corresponds to the number
matchingRow = row;
count++;
break;
}
}
}
if(count == 0) {
var lastRowNum = sheetname.getLastRow();
sheet.insertRowAfter(lastRowNum);
rowData[5] = 1;
sheetname.getRange(lastRowNum + 1, 1, 1, rowData.length).setValues([rowData]);
} else {
sheetname.getRange(parseInt(matchingRow) + 1, 6).setValue(count + 1);
}
return count + 1;
}
I've figured it out. In case this helps someone in future, here's the code. Here, count is the value I want to check before I add a new entry or update an existing entry.
function copyRowToSheetWithoutDuplicate(sheetId, tabName, rowData) {
var sheet = SpreadsheetApp.openById(sheetId);
var sheetname = sheet.getSheetByName(tabName);
var count = 0;
var existingCount = 0;
var existingEntryRow = 0;
var allValues = sheetname.getDataRange().getValues();
var numColumns = sheetname.getLastColumn();
var numRows = sheetname.getLastRow();
for(var i=0;i<numRows;i++) {
var dateFromRowData = rowData[3].substring(0,10);
var todayDate = Utilities.formatDate(new Date(), "GMT+05:30", "''yyyy-MM-dd");
if(allValues[i][1]==rowData[1] && dateFromRowData!=todayDate) {
count++;
existingCount = allValues[i][6];
existingEntryRow = i + 1;
break;
}
}
if(count == 0) {
// insert a row and make an entry
sheetname.insertRowAfter(numRows);
rowData.push(1); // count
sheetname.getRange(numRows + 1, 1, 1, rowData.length).setValues([rowData]);
return 1;
} else {
// update the existing count by incrementing it by 1
existingCount++;
rowData.push(existingCount); // count
sheetname.getRange(existingEntryRow, 1, 1, rowData.length).setValues([rowData]);
return existingCount;
}
}

Search for values in arrays

I've a spreadsheet with two sheets (Foglio1, Foglio2)
Foglio1 has five rows of numeric values in two columns, Foglio2 2 has five rows of numeric values in the first column.
I'd like to search for the Foglio2 values in Foglio1 and if it finds something copy the respective value of Foglio1, column2 into Foglio2, column2.
This is what I've got so far but it doesn't seems to work, basically it doesn't find anything so the result array remains empty
function cercaCopia() {
var ss = SpreadsheetApp.openById('myfileIDhere');
var data1 = ss.getSheetByName('Foglio2').getRange(1, 1, 5).getValues();
var data2 = ss.getSheetByName('Foglio1').getRange(1, 1, 5, 2).getValues();
var result = new Array(4);
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
if (data1[i] == data2[j, 0]) {
result[i] = data2[j, 1]
}
}
}
}
This works:
function myFunction() {
var ss = SpreadsheetApp.openById('sheetid');
var data1 = ss.getSheetByName('Foglio2').getRange(1, 1, 5).getValues();
var data2 = ss.getSheetByName('Foglio1').getRange(1, 1, 5, 2).getValues();
for (var j=0; j<5;j++){
for (var i = 0; i < 5; i++) {
if (data1[j][0]==data2[i][0]){
var range= ss.getSheetByName('Foglio2').getRange(1,1,5,5);
range.getCell(j+1,2).setValue(data2[i][1]);
}
}
}
}
I get this as a result:
In Foglio1 I had this:

Google Sheet script getValues and when find the given value, replace the value of the above row

How can I modify the script below to read the values in a column, but only read the values of the specified cells: L9, L10 ... L18, L19 .... L27, L28 .... (two by two jumping eight, to the end of the column L2000) and when find the value "1" in one of those cells, then replace the value that is 4 lines up with "FALSE"?
e.g. if cell L19 is found the number 1, then replace the value of cell L15 with the word "FALSE" ... and if cell L28 is found the number 1, then replace the value of cell L24 with the word "FALSE"
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ABC");
var lastrow = ss.getLastRow();
var range = ss.getRange(9, 12, lastrow - 1, 1);
var data = range.getValues();
for (var i=0; i < data.length; i++) {
if (data[i][0] == "1") {
data[i][0] = "FALSE";
}
}
range.setValues(data);
Add another counter to loop: Try Modifying
From:
var range = ss.getRange(9, 12, lastrow - 1, 1);
var data = range.getValues();
for (var i=0; i < data.length; i++) {
if (data[i][0] == "1") {
data[i][0] = "FALSE";
}
}
To:
var range = ss.getRange(5, 12, lastrow+5, 1);
var data = range.getValues();
var m= 0;
for (var i=4; i < data.length; ) {
if (data[i][0] == "1") {
data[i-4][0] = "FALSE";
}
m++;
if (m == 2) {
m=0;
i = i + 8;
} else {
i++;
}
}
My take on what the loop should become:
var range = ss.getRange(9, 12, lastrow - 1, 1);
var data = range.getValues();
var i = 4,
// Subtract 1 here because we compare both i and i+1 in the same loop body
len = data.length - 1;
while (i < len) {
// Check the value of the first row in the subset.
if (data[i][0] == 1) {
data[i - 4][0] = "FALSE";
}
// Check the next row in the subset.
if (data[++i][0] == 1) {
data[i - 4][0] = "FALSE";
}
// Advance to the next subset.
i += 8;
}