Apps Script for Google Sheets: setValues parameters - javascript

I've been writing a script to copy some data from an input sheet to a database to keep track of some data. I've managed to successfully write the code for linear arrays (only one row) but when I try to copy an entire 15x15 cells range I get an error stating the parameters are not correct (suggesting the dimension of the arrays are not correct) but I can't seem to understand why.
I tried both copying directly the entire 15x15 range and creating a for loop to copy row by row 15 times but I can't mangage to make it work.
Here is the main structure:
// active spreadsheet and source + target sheets
const activeSheet = SpreadsheetApp.getActiveSpreadsheet();
const srcSheet = activeSheet.getSheetByName('Data Entry');
const dstTOTSheet = activeSheet.getSheetByName('DataBaseTOT');
var firstEmptyRowTOT = dstTOTSheet.getLastRow()+1;
For loop test:
for (var i=0; i=14;i=i+1) {
// source cells
var RoundInfo = srcSheet.getRange(10+i, 2, 1, 15); // 15x15 B10:P24
// target cells
var dstTOTRoundInfo = dstTOTSheet.getRange(firstEmptyRowTOT + i, 21, 1, 15); // I am starting from column 21 because of some other data
// set value
dstTOTRoundInfo.setValues(RoundInfo);
}
Direct 15x15 test:
// source cells
var RoundInfo = srcSheet.getRange("B10:P24"); // 15x15 B10:P24
// target cells
var dstTOTRoundInfo = dstTOTSheet.getRange(firstEmptyRowTOT, 21, 15, 15);
// set value
dstTOTRoundInfo.setValues(RoundInfo);

It is actually not that difficult to understand the 2D array concept for spreadsheet, it works like this:
// inner arrary is always one row of values:
// the 1st value (array index: 0) of the inner-array is always the cell value of the 1st column of the given row.
const row_1 = ['row_value_1-1','row_value_1-2','row_value_1-3','row_value_1-4'];
const row_2 = ['row_value_2-1','row_value_2-2','row_value_2-3','row_value_2-4'];
const row_3 = ['row_value_3-1','row_value_3-2','row_value_3-3','row_value_3-4'];
// the outter-array is always the container of every rows you have in range,
// the leangth of rows (which is the leangth of each inner-array) must be the same for this structure to work in spreadsheet.
const data = [
row_1, // the 1st value (array index: 0) of the outter-array is always the 1st row,
row_2, // the 2nd value (array index: 1) of the outter-array is always the 2nd row, etc.
row_3
];
// when you loop over such kind of 2D array, it is easier to understand with a "for... of" loop than the classic "for" loop:
for (const row of data) { // << this means "for each row of the given data"
for (const col of row) { // << this means "for each column of the given row"
// this structure will go through each cell of row 1, than each cell of row 2, etc., until all rows of the given data are iterated.
console.log(col);
}
}
/**output:
row_value_1-1
row_value_1-2
row_value_1-3
row_value_1-4
row_value_2-1
row_value_2-2
row_value_2-3
row_value_2-4
row_value_3-1
row_value_3-2
row_value_3-3
row_value_3-4
*/
// and if you need to works with the indexes of some cell, you can get the index by this:
for (const [rIndex,row] of data.entries()) {
for (const [cIndex,col] of row.entries()) {
console.log(rIndex,cIndex,col);
}
}
/** output:
0 0 row_value_1-1
0 1 row_value_1-2
0 2 row_value_1-3
0 3 row_value_1-4
1 0 row_value_2-1
1 1 row_value_2-2
1 2 row_value_2-3
1 3 row_value_2-4
2 0 row_value_3-1
2 1 row_value_3-2
2 2 row_value_3-3
2 3 row_value_3-4
*/

Related

How do I iterate through my google sheets rows, update an array, and show results in another sheet?

I am trying to write a Google Apps Script function which scans every row of my spreadsheet, and if column 36 has a specific string, will look at column 31, column 23, etc. of that row to create an array with the data in these columns.
I think it will ultimately require an array updating function inside an if statement inside a loop.
Loop through every row of column 36
If string matches target string
Add row's data to an array
Update another sheet with data from this array
Thank you!
Here is what I've tried so far:
`
function myFunction() {
var sheet = SpreadsheetApp.getActive().getSheetByName("Database");
var range = sheet.getRange(2, 36).getValue();
Logger.log(range);
}
`
Here is the code, replace the string of Target Sheet Name, the specific String and etc on your own.
function myFunction() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
// DataBase sheet:
const dbSheet = ss.getSheetByName("Database");
// Target sheet for placing your outputs:
const tgSheet = ss.getSheetByName("Target Sheet Name");
// Get all values as a 2D array from DataBase sheet:
const sourceValues = dbSheet.getDataRange().getValues();
// Declare result array.
const results = [];
// Loop through every row:
for (const row of sourceValues) {
const specificString = 'Your String';
// check the value of column 36 of each row.
const check = row[36] === specificString;
// if condition check returns true, form an array with column 31, column 23 and 'etc' of this row, than push it into the result array.
if(check) results.push([row[31],row[23],'etc']);
}
// print the result onto your Target Sheet, start from A1.
tgSheet.getRange(1,1,results.length,results[0].length).setValues(results);
}
Move selected rows to another sheet
function elfunko(specificvalue) {
const ss = SpreadsheetApp.getActive();
const vs = ss.getSheetByName("Database").getDataRange().getValues().filter(r => r[35] == specificvalue);
const sh = ss.getSheetByName("Sheet1");
sh.getRange(sh.getLastRow() + 1, 1, vs.length, vs[0].length).setValues(vs);
}

Split 2D Array into multiple connected pieces by given rows and columns

I'm trying to find the most efficient way of breaking a two-dimensional array into different two-dimensional pieces, all stored into a new array which will be returned as the result.
I have a 2D array consisting of a string "N M" in the cell at row N and column M. For example, the value in row 2, column 3 would be "2 3".
Now given some rows and column indexes, I want to remove those rows and columns, and group each "piece" into its own 2D array. A "piece" is a set of orthogonally connected cells, without any intervening cells from the rows and columns removed above.
e.g. if I have an array with 8 rows and 7 columns, and I try to remove the rows [2, 3, 5], and columns [2, 6], the resulting array of "pieces" should be
[
[["0, 0", "0, 1"], ["1, 0", "1, 1"]],
[["0, 3", "0, 4", "0, 5"], ["1, 3", "1, 4", "1, 5"]],
[["4, 0", "4, 1"]],
[["4, 3", "4, 4", "4, 5"]],
[["6, 0", "6, 1"], ["7, 0", "7, 1"]],
[["6, 3", "6, 4", "6, 5"], ["7, 3", "7, 4", "7, 5"]]
]
In case the description is unclear, I went ahead and made a visual example of the process in mind here to detail the steps, problems and goals involved.
This is what I was able to come up with, but it's producing arrays that include exception columns. I know why, but I don't know how to get around it short of adding extra complexity which is not conducive to efficiency.
const SourceArray = [ ['R0·C0', 'R0·C1', 'R0·C2', 'R0·C3', 'R0·C4', 'R0·C5', 'R0·C6'],
['R1·C0', 'R1·C1', 'R1·C2', 'R1·C3', 'R1·C4', 'R1·C5', 'R1·C6'],
['R2·C0', 'R2·C1', 'R2·C2', 'R2·C3', 'R2·C4', 'R2·C5', 'R2·C6'],
['R3·C0', 'R3·C1', 'R3·C2', 'R3·C3', 'R3·C4', 'R3·C5', 'R3·C6'],
['R4·C0', 'R4·C1', 'R4·C2', 'R4·C3', 'R4·C4', 'R4·C5', 'R4·C6'],
['R5·C0', 'R5·C1', 'R5·C2', 'R5·C3', 'R5·C4', 'R5·C5', 'R5·C6'],
['R6·C0', 'R6·C1', 'R6·C2', 'R6·C3', 'R6·C4', 'R6·C5', 'R6·C6'],
['R7·C0', 'R7·C1', 'R7·C2', 'R7·C3', 'R7·C4', 'R7·C5', 'R7·C6'] ];
function split2DArray_test() {
let arrObjs = {
oldArr: {
obj: SourceArray,
bounds: { row: 8, col: 7, },
setBounds: function() {
this.bounds.row = this.obj.length;
this.bounds.col = this.obj[0].length;
},
},
newArr: { collection: [], component: [], design: { rInit: 0, rEnd: 0, cInit: 0, cEnd: 0 } },
splits: { atRows: [2, 3, 5], atCols: [2, 5] }
};
arrObjs.oldArr.setBounds();
let i = { lv1_R: 0, lv2_C: 0 , lv3_R: 0, lv4_C: 0, slicer: 0 }; let escape = false;
/*
1. Iterate through rows. (1st level)
2. If row excepted, continue iterating through rows. If not excepted, iterate through columns. (2nd level)
3. If column not excepted, RECORD ADDRESS and continue iterating through column.
4. When excepted column found, RECORD COLUMN - 1, and regin iterating through rows again (3rd level). When excepted row found, RECORD ROW - 1.
5. Record data (SliceArr): [RECORD ADDRESS[R] = RowA, RECORD ADDRESS[C] = ColA, RECORD ROW - 1 = RowB, RECORD COLUMN - 1 = ColB]
6. Create NewArr[] and SliceArrObj[]
7. Iterate through rows (r) from new SliceArr (4th level), and do SliceArrObj.push(OldArr[r].slice(ColA, ColB - ColA))
8. NewArr.push(SliceArrObj)
9. Set 2nd-level iterator to RECORD COLUMN + 1
10. Loop should work theoretically. Untested.
*/
Logger.log(`Excepting rows: ${arrObjs.splits.atRows.join(', ')}`);
Logger.log(`Excepting columns: ${arrObjs.splits.atCols.join(', ')}`);
console.time('slicer');
for (i.lv1_R = 0; i.lv1_R < arrObjs.oldArr.bounds.row; i.lv1_R++) {
if (arrObjs.splits.atRows.includes(i.lv1_R)) { continue; } else {
// We've arrived at a non-excepted row.
for (i.lv2_C = 0; i.lv2_C < arrObjs.oldArr.bounds.col; i.lv2_C++) {
if (arrObjs.splits.atCols.includes(i.lv2_C)) { continue; } else {
arrObjs.newArr.design.rInit = i.lv1_R; arrObjs.newArr.design.cInit = i.lv2_C;
Logger.log(`Found origin cell '${arrObjs.oldArr.obj[i.lv1_R][i.lv2_C]}'`);
for (i.lv3_R = arrObjs.newArr.design.rInit; i.lv3_R < arrObjs.oldArr.bounds.row; i.lv3_R++) {
if (arrObjs.splits.atRows.includes(i.lv3_R)) {
arrObjs.newArr.design.rEnd = i.lv3_R - 1;
for (i.lv4_C = arrObjs.newArr.design.cInit; i.lv4_C < arrObjs.oldArr.bounds.col; i.lv4_C++) {
if (arrObjs.splits.atCols.includes(i.lv4_C)) {
arrObjs.newArr.design.cEnd = i.lv4_C - 1;
for (i.slicer = arrObjs.newArr.design.rInit; i.slicer < arrObjs.newArr.design.rEnd + 1; i.slicer++) {
arrObjs.newArr.component.push([arrObjs.oldArr.obj[i.slicer].slice(arrObjs.newArr.design.cInit, arrObjs.newArr.design.cEnd + 1)]);
};
arrObjs.newArr.collection.push('Split'); // For the sake of output logging.
arrObjs.newArr.collection.push(arrObjs.newArr.component);
arrObjs.newArr.component = [];
i.lv2_C += 1 + arrObjs.newArr.design.cEnd - arrObjs.newArr.design.cInit;
arrObjs.newArr.design.rInit = 0; arrObjs.newArr.design.rEnd = 0; arrObjs.newArr.design.cInit = 0; arrObjs.newArr.design.cEnd = 0;
escape = true; break;
};
};
};
if (escape) { escape = false; break; };
};
};
};
i.lv2_R += 1 + arrObjs.newArr.design.rEnd - arrObjs.newArr.design.rInit;
};
};
console.timeEnd('slicer');
Logger.log(`Splitting 2D Array into:\n\n${(arrObjs.newArr.collection.join('\n\n'))}`); // The output logging.
/* Idea Space:
===== PROPOSED METHOD, INCOMPLETE =====
1. Save the origin cell of the first valid slice bounds.
2. Iterate (FOR) through columns, starting at the Bound Minimum, and going to the Bound Maximum.
3. If an exception row is found, save slice col bound max as (i.c - 1).
4. Now, iterate through rows from slice origin to next exception or bound. Once this is met, save slice row bound max as (i.r - 1).
5. Iterate through slice row bounds, pushing slices with slice column bounds as Array.slice() arguments.
6. Push new 2D array to new array collection.
=== CONCEPTS WORTH TESTING ===
I. Iterative Cell Search
Consider moving forward by setting exception or bound limits. Ex: Column iteration runs into a bound limit: slices.resume.c = false. If it's an exception, slices.resume.c = true. This way,
any time a full 2D slice is pushed to the collection of arrays, it can take the existing current slice data and either iterate across to the next horizontal array (Resume = true), or
iterate down to the next vertical array (Resume = false). If both Column and Row resume are false, then we are done adding sliced 2D arrays to the array collection.
If at least one Resume is true, then another resume can be made true. Once both are true, then a new slice origin point can be made.
II. Iterative Bounds & Exception Search.
1. Table Bounds defined and adjacent exceptions trimmed (Done).
> The current exceptions are now the top-left-side borders of the desired slices.
2. Iterate through exception indices column first (On first loop, operate from top-left bound), then row. On each, set slice start to (i.r + 1, i.c + 1), where i.? = the exception column or
row. Set the end bounds to the index represented by the next element in the list (column or row).
*/
};
In the approach I take below, I split up the problem in two main steps:
const ranges = (splits, length) => [...splits, length].reduce(
(r, e, i, a) => (s = i ? a[i - 1] + 1 : 0, e - s ? [...r, {s, e}] : r),
[]
);
const blocks = (array, rowRanges, colRanges) => rowRanges.map(
({s, e}) => array.slice(s, e)
).map(block => colRanges.map(
({s, e}) => block.map(row => row.slice(s,e))
)).flat();
The ranges() function takes an input array containing "splits" information (i.e. rowSplits or colSplits), and then uses that information to create an array of ranges for the row blocks or column blocks. Each range is defined as an object of type {s: number, e: number}, where s is the start of the range (inclusive) and e is the end (exclusive). The reduce operation used in this function makes it so that empty ranges are filtered out.
The blocks() function takes the 2D array and the row and column ranges as input. It first uses the row ranges to split the 2D array into row blocks, and then uses the column ranges to split those blocks down to the regions defined in the problem statement. The splitting operations are all implemented by mapping the ranges to their corresponding array slices. Finally, the entire result is flattened to arrive at the required output.
Complete snippet:
const rows = 8;
const cols = 7;
const arrOld = Array.from(
{ length: rows}, (_, y) => Array.from(
{ length: cols }, (_, x) => `${y}, ${x}`
)
);
const rowSplits = [2, 3, 5];
const colSplits = [2, 6];
const ranges = (splits, length) => [...splits, length].reduce(
(r, e, i, a) => (s = i ? a[i - 1] + 1 : 0, e - s ? [...r, {s, e}] : r),
[]
);
const blocks = (array, rowRanges, colRanges) => rowRanges.map(
({s, e}) => array.slice(s, e)
).map(block => colRanges.map(
({s, e}) => block.map(row => row.slice(s,e))
)).flat();
const arrNew = blocks(
arrOld,
ranges(rowSplits, rows),
ranges(colSplits, cols)
);
console.log(arrNew);

Google script loop check for duplicate before writing data

I have a script which reads data from a site, stores the data in an array variable and then writes the data to a google sheet.
Per item id (JSON format), the data which is read is of the form:
[timestamp number text1 text2]
and these details are duplicated across different ids in a for loop.
What i'm left with on the sheet is per row (one item in each cell):
timestamp(id1) number1(id1) text1(id1) text2(id1) timestamp(id2) number1(id2) text1(id2) text2(id2) timestamp(id3) number1(id3)...etc
each row will contain only a single value for timestamp, however the timestamp variable is written multiple times. Is it possible to adapt my script to check column A of the bottom row on my sheet and only write the new row if the timestamp in the current bottom row is different to the timestamp in the new row about to be written.
for loop iterates through json file and stores data in "values" variable.
{
{.....
let values = [];
values.push(timestamp, number1, text1, text2); //final line of for loop
}
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("test");
var range = ss.getRange(3, 1, 1, values.length);
if (range.isBlank()) {
range.setValues([values]);
} else {
ss.appendRow(values);
}
}
2 Requests:
a) I would like the timestamp variable to only be written once, in column A.
b) I would like the script to check the last written row to ensure that the timestamp value printed in column A is different to the value about to be written in the new row. If it is the same, do not write to the row.
Thanks
So for Request A: You need to change the array you are passing to the setValues()method. If you already know which columns these are, then you can modify the array by replacing the existing value with an empty string.
const outputRow = [ … ]; // This is where your current output is set
const emptyTheseColumns = [3, 6, 9]; // columns C, F, I
const cleanedOutputRow = outputRow.map( (item, index) => {
const column = index + 1; // columns start at 1, index at 0
// return empty string for stated columns
if( emptyTheseColumns.indexOf( column ) != -1){
return ""
}
// otherwise return the current value
return item
});
// then use the new array in setValues( cleanedOutputRow )

Appending a 2D array to another 2D array, gets inserted as a single row instead

I'm having problems to append a 2D array at the end of another 2D array.
The first array called array_from_sheet I construct from reading a range of cells in Google sheet, from a range of 5 columns and 100 rows. This first array looks like this in debug window:
[["2017-12-02T16:49:48.9Z", 1040036, 399.07, 0.01, "sell"], ["2017-12-02T16:49:48.9Z", 1040037, 399.08, 1.12907707, "sell"], [....
The second array called trades comes from a JSON string that i parsed and looks like this in debug window (it has 100 rows and 5 columns):
[["2017-12-02T18:06:55.909Z", 1040574, "399.00000000", "1.12619681", "sell"], ["2017-12-02T18:06:55.829Z", 1040573, "399.00000000", "1.31054161", "sell"], [...
this is before my splice command: trades.splice(1, 0, array_from_sheet);
After my splice command, the trades array looks like this in debug window:
[["2017-12-02T18:06:55.909Z", 1040574, "399.00000000", "1.12619681", "sell"], [["2017-12-02T16:49:48.9Z", 1040036, 399.07, 0.01, "sell"], [...
You can see the second row now starts with a double bracket, problem i guess.
The debug window says it has now 101 rows while it should have 200. The second row of the new array now has 100 elements into it, looks like array_from_sheet got inserted here instead on the same row.
(I tried the .push command but got same problem)
(array_from_sheet.length returns 100, so it appears to be 100 rows)
Here's my code:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var first = ss.getSheetByName("trades");
first.activate();
//var sheet = SpreadsheetApp.openById('DEV_GDAX Order book and trades')
//var APIPullSheet = sheet.getSheetByName("APIPull");
//first.getRange('A2:D19999').clearContent();
// call the trades from GDAX into var parcedData- should add error handling here if server does not respond or return error code
var url = "https://api.gdax.com/products/ETH-EUR/trades"
var responseAPI = UrlFetchApp.fetch(url)
var parcedData = JSON.parse(responseAPI.getContentText());
// creates new empty array trades, adds headers on first row,
var trades = new Array ();
//trades.push(['time', 'trade_id', 'price', 'size','side']) hll: i removed this line as i don't need headers, i have them on row1 in sheet
// create temp array for a row, read from parsedData, push to temp, repeat, then push to temp (more or less)
var keys = ['time', 'trade_id', 'price', 'size','side'];
for (var i in parcedData) {
var temp = [];
for (var j in keys) {
temp.push(parcedData[i][keys[j]]);
}
trades.push(temp);
//Logger.log(trades)
}
//here i should remove duplicates first before coyping to sheet trades
//load current trades on sheet in temp array
var lastRow = first.getLastRow();
var array_from_sheet = first.getRange('A2:E' + lastRow).getValues();
Logger.log("array-from-sheet: ");
Logger.log(array_from_sheet);
//append new trades array to array_from_sheet
trades.splice(1, 0, array_from_sheet);
Solved:
I used this command:
trades_new = array_from_sheet.concat(trades);
instead of:
trades.splice(1, 0, array_from_sheet);
now trades_new contains the 2 arrays end to end.

Dynamically Validating Multiple Google Sheet Tabs

I am writing a script for google sheet validation on localization tests. I've gotten stuck on some of the logic. The purpose of the script is to 1) Iterate through all tabs. 2) Find the column on row 2 that has the text "Pass/Fail". Lastly, 3) Iterate down that column and return the rows that say Fail.
The correct script to look at is called combined(). Step 1 is close to being correct, I think. Step 2 has been hard coded for the moment and is not dynamic searching the row for the text. Step 3 is done.
Any help would be great :)!!! Thanks in advance.
https://docs.google.com/spreadsheets/d/1mJfDtAi0hHqhqNB2367OPyNFgSPa_tW9l1akByaTSEk/edit?usp=sharing
/*This function is to cycle through all spreadsheets.
On each spreadsheet, it will search the second row for the column that says "Pass/Fail".
Lastly, it will take that column and look for all the fails and return that row*/
function combined() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var r =[];
for (var i=0 ; i<sheets.length ; i++){//iterate through all the sheets
var sh = SpreadsheetApp.getActiveSheet();
var data = sh.getDataRange().getValues(); // read all data in the sheet
//r.push("test1"); //Testing to make sure all sheets get cycled through
/*I need something here to find which column on row two says "Pass/Fail"*/
for(i=3;i<data.length;++i){ // iterate row by row and examine data in column A
//r.push("test2"); //Testing to make sure the all
if(data[i][7]=='Fail'){ r.push(data[i])}; // if column 7 contains 'fail' then add it to the list
}
}
return r; //Return row of failed results on all tabs
}
At first, it retrieves data at column g. It retrieves a result from the data. The result is 2 dimensional array. The index of each element of the 2D array means the sheet index. If the sheet doesn't include values in column g, the element length is 0.
For example, in the case of following situation,
Sheet 0 doesn't include values in column g.
Sheet 1 includes values in column g. There are "Fail" value at the row number of 3, 4, 5.
Sheet 2 includes values in column g. There are "Fail" value at the row number of 6, 7, 8.
The result (return r) becomes below.
[[], [3, 4, 5], [6, 7, 8]]
Sample script 1:
function combined() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var data =[];
sheets.forEach(function(ss){
try { // In the case of check all sheets, if new sheet is included in the spreadsheet, an error occurs. This ``try...catch`` is used to avoid the error.
data.push(ss.getRange(3, 7, ss.getLastRow(), 1).getValues());
} catch(e) {
data.push([]);
}
});
var r = [];
data.forEach(function(e1, i1){
var temp = [];
e1.forEach(function(e2, i2){
if (e2[0] == "Fail") temp.push(i2 + 3);
});
r.push(temp);
});
return r;
}
If I misunderstand your question, I'm sorry.

Categories

Resources