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.
Related
I'm trying to shorten my script time, as it is reaching the apps script time limit too often (1800s). Therefore, I'm trying to reduce the number of loops the script is performing.
The script goal is to collect all Montecarlo Revenue analysis results, and yo do so it iterates 1000 I (E.g. 1000) times. Each iterations collects the following data points: Total Revenue, # of logos and the same per month.
I've managed to do that through creating a Loop in a Loop (Loopin I for the Montecarlo iterations, and looping J through each data point) and creating a 2D array that later I post in my sheet using SetValues.
for (var I=0; I < Runs; I++) {
MCOutput[I] = new Array(DataLength);
MCOutput[I][0] = I+1;
sheet.getRange(6,18).setValue((I+1)/Runs);
for (var J=1; J<DataLength; J++) {
MCOutput[I][J]=sheet.getRange(5,J+StartCol).getValue();
}
sheet.getRange(Row,StartCol,MCOutput.length,MCOutput[0].length).setValues(MCOutput);
My goal is to reduce the running time, by looping only once and collecting all the data through GetValues. I've managed to do so, but I can't find a way to set these values to a 2D array in the sheet. I'm getting the following error:
Exception: The number of columns in the data does not match the number of columns in the range. The data has 1 but the range has 21.
Here is the script for it:
var MCOutput = [];
for (var I=0; I < Runs; I++) {
MCOutput[I] = new Array(DataLength);
sheet.getRange(6,18).setValue((I+1)/Runs);
sheet.getRange(5,StartCol).setValue(I+1);
MCOutput[I]=sheet.getRange(5,StartCol,1,DataLength).getValues();
}
sheet.getRange(Row,StartCol,I,DataLength).setValues(MCOutput);
I wasn't able to solve it through map or splice, I guess it is because my 1D array has rows and columns of data.
Here are some modification I would suggest.
new Array() can slow down a script and really isn't needed here.
getValues() returns a 2D array so you need to get the first row getValues()[0] of it and push it into the MCOutput array.
sheet.getRange(6,18).setValue((I+1)/Runs); does absolutely nothing that I can see and multiple calls to setValue() can really slow down a script.
you can simple replace the value in MCOutput[I][0] = I+1;
// you are always getting the same row and StartCol or Datalength don't change
let values = sheet.getRange(5,StartCol,1,DataLength).getValues()[0];
let MCOutput = [];
for (let I=0; I < Runs; I++) {
// assuming values is only javascript primitives a shallow copy will do
MCOutput.push(values.map( value => value ));
MCOutput[I][0] = I+1;
}
sheet.getRange(Row,StartCol,I,DataLength).setValues(MCOutput);
Reference:
Best Practices
Array.push()
new Array() vs []
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);
I have a Google form that collects responses in a linked spreadsheet. One of the fields in the form is Username (column B in the sheet). I'm writing a script to notify me when a new user (unique username) makes a submission. To do this, I:
Get all the values in column B except the latest one
Get the latest username
Check if the latest username is in the list of values (do something if not).
Here's what I've got so far:
function isUserNew() {
var spreadsheet = SpreadsheetApp.openById("INSERT ID HERE");
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]);
var last_row_number = sheet.getLastRow()
var penultimate_row_number = (last_row_number-1)
var latest_username = sheet.getRange(last_row_number, 2).getValue()
var usernames = sheet.getRange("B2:B" + penultimate_row_number).getValues();
}
This gives me a 2D array usernames that looks like [[UserA],[UserB],[UserC],[UserC],[UserA],[UserX]]. Then I tried if (usernames.indexOf(latest_username) == -1 but this doesn't work - it only works for a 1D array. I thought of converting the array to 1D using a loop:
for (var i = 0; i < usernames.length; i++) {
Logger.log(usernames[i][0]);
}
This makes the logger correctly log all the usernames I want, but I don't know how to put them into an array.
2 main questions:
How do I get the values into an array instead of the Logger?
Is this the best way to do what I'm trying to do? It seems unnecessary that every time there's a submission, I grab a 2D array, convert to 1D, and compare. The column data is not going to change, it only grows by 1 every time. How will this impact run time as the data grows into the 1000s?
Yep, you could flatten the array and compare with indexOf as you're doing:
var flattened_usernames = [] ;
for (var i = 0; i < usernames.length; i++) {
flattened_usernames.push(usernames[i][0]);
}
But you're better off just doing the check inside the loop:
if (usernames[i][0] === latest_username)
By the way, I assume you know how to grab the username directly from the onFormSubmit event rather than grabbing it from the last row of the sheet. But if not you should learn how to do that!
Instead of:
var usernames = sheet.getRange("B2:B" + penultimate_row_number).getValues();
Do:
var usernames = [].concat(...sheet.getRange("B2:B" + penultimate_row_number).getValues());
This flattens the 2d result of a column into a 1d array. If it's not the shortest way it should be close. I don't know about the efficiency.
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);
}
}
I have a script that I want to use so that my manager can quickly see which items in the Spreadsheet need parts ordered. The script quickly and easily hides columns containing information not pertinent to ordering parts, and then hides all rows (out of thousands) where the value in column S is FALSE (doesn't need parts ordered). The hiding columns part is almost instant, but the hiding rows part is EXTREMELY slow. I understand that in order to speed it up, the data should be loaded into an array, then the loop will run on the array in memory instead of making many calls to the spreadsheet. I have seen similar questions, but the answers don't seem to explain exactly how to do this. One example I read suggested that this is already using an array, which confused me even more. Any help pointing me in the right direction would be appreciated. Here is the script I'm using:
function showPartsNeeded() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getRange('S:S').getValues();
for(var i=1; i< data.length; i++){
if(data[i][0] == false){
sheet.hideRows(i);
}
sheet.hideColumns(2,2);
sheet.hideColumns(5,2);
sheet.hideColumns(8,1);
sheet.hideColumns(10,2);
sheet.hideColumns(13,18);
sheet.hideColumns(47,14);
}
}
I have tried the following to load the rows that have the "Needs Parts" column marked as false into an array, then hide only the rows that are present in the array, but I only get a "Cannot find method getRange()." line 15 error and I don't understand why:
function showPartsNeeded() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getRange().getValues();
sheet.hideColumns(2,2);
sheet.hideColumns(5,2);
sheet.hideColumns(8,1);
sheet.hideColumns(10,2);
sheet.hideColumns(13,18);
sheet.hideColumns(47,14);
var temp = [];
for (var i = 1; i< data.length; i++ ) {
if (data[i][19] == false) {
temp.push.data[i];
}
}
if (temp.length > 0 ) {
sheet.hideRows(temp);
}
}
I am fairly sure the sheet.hideRows(temp); line is wrong but I'm still trying to figure out how to use the data in the array with hideRows().
You are looping through thousands of rows and every time you find one row with the condition you call sheet.hideRows(i). Calls to the sheet functions or ranges are very slow and that's why it is recommended to do all the operations in an array and then just insert the changes as one operation in the case of ranges.
In this case you could probably improve your code by grouping consecutive rows that follow the condition and instead of calling sheet.hideRows(i) on each of them, you could call sheet.hideRows(first_row, last_row).
So if you have 10 consecutive rows that have the condition, instead of making 10 calls, you would just make one. eg. sheet.hideRows(20,30);