Problem with copying volatile data values to rows of a google sheet - arrays

I am trying to convert an old Excel VBA program to Google Sheets for projection 30 years of financial results. The key issue is the VBA Excel sheet haw a row (a1:ad1) of (30) cells that each contain the volatile function Rand() to generate a random number in each cell through each of 1000 loops:
In each loop these cells 1) reset their random number, then 2) some rows of calculations are made based on the new random numbers, then 3) then a resulting row of 30 new values in copy-pasted (values only) to another part of the sheet so the individual results can be statistically analyzed.
The copy-paste in google sheets is too slow and I hit a 6 minute execution time out, so I am trying to accumulate each loop into a single 2D array, each row being one of the 1000 loop results in 3 above,then write the entire array back to the sheet at once. To get the 30 cells to recalc, each time I read a row, I force a new value to be set in a single cell of the spreadsheet at the beginning of each loop. That seems to work, but trying move the data from the row to the accumulation array and then writing the values back to individual rows in the sheet at once, I always wind up with arrays the have too many dimensions some other problems getting the data back to the sheet as a 1000 x 30 range of static data.
Here's the basic outline of where I am so far after many variations of this :
function test1(){
var sheet=SpreadsheetApp.getActive().getSheetByName('UST_Results'),
range;
var values=[];
var LargeArray= Array(new Array()) //new Array()
for (var i=0;i<3;i++) { // using a small number (3) rows to get it work
values =getRandRow(sheet)[0][i];
LargeArray(LargeArray.length) = JSON.stringify(values)
};
};
function getRandRow(sheet) {
var values_array =[];
let myData =[];
var cell = 0;
// force the Rand() to recalc before getting the next copy of A1:Ad1
for (var i=0;i<1;i++) {
var cell = sheet.getRange(i+5,1);
cell.setValue(i);
SpreadsheetApp.flush();
}
range = sheet.getRange('A1:ad1');
values_array = range.getValues();
myData.push(values_array);
return myData;
}
A picture of the sheet testing with just the rand numbers and not the derived row.
enter image description here

Related

How do I count the number of rows in an array?

My task is to grab a 2-dimensional table from cells on a worksheet into a 2-dimensional array, delete some or all of the rows (right terminology?) from testing, and then paste what's left into a worksheet.
To determine the range for pasting I need to know the length of the edited array. This is where I'm challenged.
// This gets the array which is 3 columns wide and X rows (X will vary)
var termEmp = spreadsheet.getRangeByName("roeList").getValues();
// e.g. termEmp = [ ["Bob", 1, "day", "key"] ["Cindy", 2, "day", "it"] ["Laura", 1, "night", "we"] ]
// Then I find the number of rows that actually have data
numRows = termEmp[0].length; // result = 3
// A for loop with counter i tests if the second element equals 2 of each row and deletes each array row if it's there
// In this example I want to delete the row with Cindy because of the 2
// To do this is use the splice method to delete the second row thusly:
termEmp.splice(i,1); // i = 1 in the for loop
// After testing all elements, and deleting the rows I want, I then need to count the number of rows remaining (to create a range for pasting into the worksheet)
numRows = termEmp[0].length;
// This is SUPPOSED to count the number of rows remaining (first element is ALWAYS non-blank)
Here's my problem. For this example the number of rows after the splice goes from 3 to 2. I looked at the array to confirm this.
But in my code termEmp[0].length STAYS at 3. I can't use it to define my range for pasting.
What's needed to get the count right?
For number of rows, you can get the length of the full array.
var numRows = termEmp.length
What you're getting with termEmp[0].length is the number of columns in first row.
EDIT
OP indicated the answer "doesn't work" (which is false) however, as a courtesy here's subsequent code that helps his followup question to identify members in an array contain another array (effectively 2-dimensional spreadsheet data). The below code will take all memembers from termEmp that are an array, and inserts them into cleanedArray.
var cleanedArray = [];
for(var i=0;i<termEmp.length;i++){
var singleMember = termEmp[i];
if(Array.isArray(singleMember)){
//makes a clean array with only 2d values
cleanedArray.push(singleMember);
}
}
var numberOfMembers = cleanedArray.length;
Logger.log(numberOfMembers);

Filter a 2D array using another 1D array

I am trying to use arrays to clean up a list of package tracking updates. The data is organised in 3 columns:
| Tracking No | Status | Detail |
The scripts first reads all the data from the sheet, then derives a list of tracking numbers which have been delivered. It then needs to use this array to remove all instances of this tracking number from the original array to create a new array. Finally, both arrays are written to 2 different sheet.
The code ran perfectly a few times, but now is failing now. Any help to find the fault is appreciated. The 1st two arrays are created without any issues, but the 3rd array is not created as expected, the tracking numbers which exist in the 2nd array are not being removed.
I am new to scripting, and most of this code is copied from the internet, so any other tips are welcome as well! I know it can be done by looping, but the dataset is large (15000 lines), and arrays seem to work faster than looping.
function identifydelivered2() {
spreadsheet = SpreadsheetApp.getActive();
sheet = spreadsheet.getSheetByName("Sheet1");
targetsheet = spreadsheet.getSheetByName('Delivered')
//find last row in delivered sheet
var targlrow = targetsheet.getLastRow() + 1;
//find last row in Sheet1
var lastrow = sheet.getLastRow();
var capturerange = [[]];
//read all values from Sheet1
capturerange = sheet.getRange(2, 1, lastrow, 3).getValues();
//stores number of rows captured
var clearlen = capturerange.length
//creates a filtered 2D array of all the delivered rows
let delivered = capturerange.filter(dataRow => dataRow[1] === 'Delivered' || dataRow[1] === 'delivered');
//creates a 1D matrix of all tracking numbers which have been delivered
let deliverednos = delivered.map(x => x[0]);
//creates a filtered 2D array of numbers not in deliverednos array
let intransit = capturerange.filter(dataRow => dataRow[0] != deliverednos);
try{
//write delivered rows to new sheet
targetsheet.getRange(targlrow,1,delivered.length,3).setValues(delivered);
//clear contents of main sheet
sheet.getRange(2,1,clearlen,3).clearContent();
//write in-transit rows to sheet1
sheet.getRange(2,1,intransit.length,3).setValues(intransit);
}
catch(e){
Browser.msgBox('No delivered packages found!')
}
}
Use Set to create a set of deliverednos.
const deliverednos = new Set(delivered.map(x => x[0]));
//creates a filtered 2D array of numbers not in deliverednos array
const intransit = capturerange.filter(dataRow => !deliverednos.has(dataRow[0]));

Copy and Pasting a Moving Range in Google Sheets

I'm very much still learning the basics of script writing, but I have a rather specific question that I've not been able to find a solution for - perhaps because I might be phrasing it wrong.
Essentially, In sheet MS, I have a range of data (A2:O23) with formulas that I want to stay fixed (as in, always reference the same range in a different sheet).
My aim is, at the run of a script, that this block of formulas will copy and paste from the next empty row (in the same columns [A:O]) and that the previous block (A2:O23) will copy and paste over itself as values.
Then, each time the script runs, it does the same thing, copying the latest block of 22 rows beneath itself and pasting itself as values. Essentially having the effect of building a long list of data with the latest (read lowest) range being the formulas and all above being values.
For example, the results of the first time it runs should be that, A24:O45now show the same formula and that A2:O23 are showing as values only. The next time, A24:O45 would be frozen as values and A46:O67 are now the formulas.
I'm not sure if this requires the use of loops or there is a different way to achieve the effect, but the former is something I've not used and the latter is still a mystery to me.
I realise that this might not be especially clear, but I'm happy to field any questions and greatly appreciate any efforts made to help.
Thanks
If I understand you correctly, you want to:
Copy the last 22 rows of your sheet (columns A to O) to the next 22 rows (formulas included).
Copy/paste to the original range as values only.
If the above is correct, then you can copy and run the following code to the script bound to your spreadsheet (see inline comments for detail on what the code is doing, step by step):
function copyPasteValues() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("MS"); // Name of your sheet (change if that's necessary)
var lastRow = sheet.getLastRow();
var firstCol = 1; // First column to copy
var numCols = 15; // Number of columns to copy
var numRows = 22; // Number of rows to copy
if (lastRow > numRows) { // Check if the sheet has at least 23 rows with content (otherwise it would give error when copying the range)
var originRange = sheet.getRange(lastRow - numRows + 1, firstCol, numRows, numCols);
var destRange = sheet.getRange(lastRow + 1, firstCol, numRows, numCols);
originRange.copyTo(destRange); // Copying with formulas to next 22 rows
originRange.copyTo(originRange, {contentsOnly:true}); // Copying only values to same range
}
}
The main method to notice here is copyTo, which can be used to copy the data as it is, with formulas included, or values only, by setting the parameter {contentsOnly:true} (copyValuesToRange could be used too).
I hope this is of any help.
function copypastepaste() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rows=sh.getLastRow();
if(rows>22) {
var rg=sh.getRange(sh.getLastRow()-21,1,22,16);
var vA=rg.getDisplayValues();
var fA=rg.getFormulas();
var nrg=sh.getRange(sh.getLastRow()+1,1,22,16);
nrg.setFormulas(fA);
rg.setValues(vA);
}
}

Google Script Overwriting Entire Sheet, Not Specific Range

In the function below, I grab data that is in multiple columns via a form response on my second sheet and place the information on my first sheet organized in rows.
I would like to have the first blank column after the data, currently G on my new sheet editable so that someone can come in and "approve" the contents of each row. Right now, when this script runs, it overwrites the contents of Column G. I thought the number 6 in the line with sh0.getRange(2, 1, aMain.length, 6).setValues(aMain); was telling the script to only put data into 6 columns... looks like that's not the case.
I also thought that I may be able to do a workaround by changing that line to sh0.getRange(2, 2 ... it would let me keep the first column as an editable column... that didn't work either.
Any suggestions to allow me to use this script and keep a column editable?
function SPLIT() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh0 = ss.getSheets()[0], sh1 = ss.getSheets()[1];
// get data from sheet 2
var data = sh1.getDataRange().getValues();
// create array to hold data
var aMain = new Array();
// itterate through data and add to array
// i is the loop, j=3 is the column it starts to loop with, j<9 tells it where to stop.
// in the aMain.push line, use data[i][j] for the rows to search and put in the one column.
for(var i=1, dLen=data.length; i<dLen; i++) {
for(var j=5; j<9; j++) {
aMain.push([data[i][0],data[i][1],data[i][2],data[i][3],data[i][4],data[i][j]]);
}
// add array of data to first sheet
// in the last line, change the last number to equal the number of columns in your final sheet.
// the first number in getrange is the row the data starts on... 1 is column.
sh0.getRange(2, 1, aMain.length, 6).setValues(aMain);
}
}

Having trouble understanding multidimensional arrays in as3

Let's say I have some code like:
private function makeGrid():void
{
_grid = new Array();
for(var i:int = 0; i < stage.stageWidth / GRID_SIZE; i++)
{
_grid[i] = new Array();
for(var j:int = 0; j < stage.stageHeight / GRID_SIZE; j++)
{
_grid[i][j] = new Array();
}
}
}
I don't quite understand what's going on. I get that in the first for loop it determines the number of columns needed, and in the second it determines rows, but I don't get why I'm making arrays out of _grid[i] and _grid[i][j].
For instance, _grid[i] = new Array(); get's called 16 times (800px/50px), so that would make 16 arrays right? Why do I need those if the second for loops is already calculating the amount of rows I need?
I'm just going to elaborate on what has already been commented. Let's say that you are creating a 2D grid formed of rows and columns and you wanted to store some sort of data at each "cell" or specified index of the grid.
The first step is to create the first array to hold either the rows or columns (which you choose first doesn't really matter as you can adjust the for loops either way).
The first for loop creates a new row, then in the next inner loop you fill all the columns of that row (if we had chosen columns to be created first then we would fill all the rows of the columns). In this case the inner loop is creating all the columns with another array (making it a 3-dimensional array as mentioned in the comments).
The reason for doing this is for organization and easy look up. If you wanted to see the data stored in the 1st column of the 3rd row it would be as easy as doing _gird[2][0].
Now as to why a 3rd dimension is made as in _grid[i][j] = new Array(); that is specific to what kind of data needs to be stored at that row and column.

Resources