How slow can the google sheets api be? [duplicate] - google-apps-script

This question already has answers here:
How much faster are arrays than accessing Google Sheets cells within Google Scripts?
(2 answers)
Closed 3 years ago.
In my script I iterate through cells each of which represents a day on The calendar grid. So it goes from an earlier date (from a cell changed by the user) to a later one. Top down and left to right.
The script starts when a user changed value of a cell. And it must fill every subsequent days-cells by the same value up to certain cell. Lets say it must stop on the cell with red font. Thus every iteration the script must get the cell font color.
...Or, The iterations must stop when the script gets a cell representing a certain date. Thus every iteration the script must verify which date the cell represets. To understand what date a cell represents I get The values from the helper cells (headers) and use getValue(). Whatever.
Everything is bearable: looping through cells, changing the values in each cell, getting helper cells(ranges). But! As soon as I add getValue() to the given headers it starts to work unbelievably slowly. Or even I just get font color... Any function starting from "get" included in iteration makes the job unbelievably slowly!
A script with looping as many as you like getRange(), setValue() works out in tolerable time, but with just one getFontColor() or getValue this job runs in the same time for just one cell.
Either I do somethin illegal or google ?
Is there an opportunity to accelerate this job significantly?
Or job like this should be done quite differently?
function onEdit(evt) {
var aSheet = evt.source.getActiveSheet();
// veryfy which sheet
switch( aSheet.getName().toLowerCase() ) {
case "wage":
// get range - calendar grid
var wageGrid = aSheet.getParent().getRangeByName("wageGrid");
var editedCell = evt.range;
// loop exit flag
var weBreak = false;
editedCell.setFontColor("red");
// loop through rows
for(var rowIndex = editedCell.getRow(); rowIndex <= wageGrid.getLastRow(); rowIndex++) { if(weBreak) break;
// loop through columns
for(var collIndex = (rowIndex == editedCell.getRow())?editedCell.getColumn():wageGrid.getColumn(); collIndex <= wageGrid.getLastColumn(); collIndex++) {
// as many as you like
var currentLoopCell = aSheet.getRange(rowIndex, collIndex);
var dayHeaderCell = aSheet.getRange(rowIndex, 1);
var monthHeaderCell = aSheet.getRange(1, collIndex);
cell.setValue(evt.value);
// but getValue() or getSomeAttribute() will slow down the process
//var cellFontColor = cell.getFontColor();
//if(cellFontColor=="red") weBreak = true; break;
}
}
break;
case "nonexistentyet":
break;
default:
Logger.log("What was it?")
}
}

What you have encountered is normal behaviour. Each call to the sheet such as getValue() and getFontColor() takes a fair amount of time, often 1 to 2 seconds each. You should avoid calling these functions in a loop.
When you want to loop over a large set of cells and work with their values, define the entire range with getRange() and use getValues() and getFontColors() instead. These functions will return the data from an entire range in a two-dimensional array. You can even bring in all the data in the sheet with getDataRange().
Similarly, it is best to write in blocks as well, using setValues() and setFontColors() rather than setting values/colors on cells one by one

Related

Static timestamping in Google Sheets

I am trying to add STATIC timestamp to my data whenever it is imported or pasted in the sheets.
I am using this formula now
(=ARRAYFORMULA( IFS(I1:I="","",L1:L="",NOW(),TRUE,L1:L)))
but, whenever I open the sheet again the time gets changed automatically to the current time as i am using the now() function. I tried on-Edit in the script, but it's only working when the data is manually entered.
Is there any other way I can use to static timestamp when data is being pasted or imported?
Instead of NOW() on the formula, do it via script using new Date().
The NOW() function updates the timestamp every time the spreadsheet is open or something changes in it, while the new Date() gives you a full (date and time) and static timestamp.
Also, as I've seen on the comments of your question, there really is no way to use onEdit() through automated scripts and macros.
Answer
You can use a custom function to return the actual date with the method new Date() and the Properties Service. Open Apps Script and paste the following function:
Code
function getTimestamp(reset) {
// update the timestamp
if (reset == 1) {
setTime()
}
// try-catch structure in order to set the time in the first execution
try {
var time = ScriptProperties.getProperty('time')
}
catch (err) {
setTime()
var time = ScriptProperties.getProperty('time')
}
return time
}
function setTime() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var time = new Date()
ScriptProperties.setProperty('time', time)
}
How it works
Now, you can use it in any cell like another Sheet function. Call the function with =getTimestamp(0). On the first execution, it tries to get the saved property time, but as the property does not exist it generates a timestamp and saves a new property in the project with the key time and the value of the timestamp.
In the following executions, the value obtained by the function when it is recalculated is the same, since the property is not overwritten unless the function is called with a 1 input: =getTimestamp(1). In this case, the timestamp is updated, but if it is not set back to =getTimestamp(0), every time the function is recalculated (which happens automatically every so often) the timestamp will change.
In conclusion, always use =getTimestamp(0). When you want to update the value, change it to =getTimestamp(1) and go back to the original formula.
update
I have updated the answer to explain how to update the timestamp when new values are added:
Use a cell as input to the function, e.g. =getTimeStamp(A1) 2.
Create an onEdit trigger
Check that the range of the e event belongs to new values.
Update the value of A1 to 1 and then to 0 if you have detected new values.
example:
function onEdit(e){
var range = e.range
var cell = SpreadsheetApp.getActiveSpreadsheet().getRange('A4')
if (range.columnStart > 1 && range.rowStart > 10){
cell.setValue(1)
SpreadsheetApp.flush()
cell.setValue(0)
}
}
If new values are added from column 1 and row 10, A1 is updated to 1 and then to 0, thus updating the value of the timeStamp function and saving it permanently until the trigger is executed again.
References:
Custom Functions in Google Sheets
Working with Dates and Times
Apps Script: Extending Google Sheets
Properties Service
Not sure have your question got a solution. I had the same struggle as yours over the year, especially with pasted data, and I found a solution that works for my case nicely (but not by formula, need to run in Apps Script).
Some background for my case:
I have multiple sheets in the spreadsheet to run and generate the
timestamp
I want to skip my first sheet without running to generate timestamp
in it
I want every edit, even if each value that I paste from Excel to
generate timestamp
I want the timestamp to be individual, each row have their own
timestamp precise to every second
I don't want a total refresh of the entire sheet timestamp when I am
editing any other row
I have a column that is a MUST FILL value to justify whether the
timestamp needs to be generated for that particular row
I want to specify my timestamp on a dedicated column only
function timestamp() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const totalSheet = ss.getSheets();
for (let a=1; a<totalSheet.length; a++) {
let sheet = ss.getSheets()[a];
let range = sheet.getDataRange();
let values = range.getValues();
function autoCount() {
let rowCount;
for (let i = 0; i < values.length; i++) {
rowCount = i
if (values[i][0] === '') {
break;
}
}
return rowCount
}
rowNum = autoCount()
for(let j=1; j<rowNum+1; j++){
if (sheet.getRange(j+1,7).getValue() === '') {
sheet.getRange(j+1,7).setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm:ss");
}
}
}
}
Explanation
First, I made a const totalSheet with getSheets() and run it
with a for loop. That is to identify the total number of sheets
inside that spreadsheet. Take note, in here, I made let a=1;
supposed all JavaScript the same, starts with 0, value 1 is to
skip the first sheet and run on the second sheet onwards
then, you will notice a function let sheet = ss.getSheets()[a]
inside the loop. Take note, it is not supposed to use const if
your value inside the variable is constantly changing, so use
let instead will work fine.
then, you will see a function autoCount(). That is to make a for
loop to count the number of rows that have values edited in it. The
if (values[i][0] === '') is to navigate the script to search
through the entire sheet that has value, looking at the row i and
the column 0. Here, the 0 is indicating the first column of the
sheet, and the i is the row of the sheet. Yes, it works like a
json object with panda feeling.
then, you found the number of rows that are edited by running the
autoCount(). Give it a rowNum variable to contain the result.
then, pass that rowNum into a new for loop, and use if (sheeet.getRange(j+1,7).getValue() === '') to determine which row
has not been edited with timestamp. Take note, where the 7 here
indicating the 7th column of the sheet is the place that I want a
timestamp.
inside the for loop, is to setValue with date in a specified
format of ("yyyy-MM-dd hh:mm:ss"). You are free to edit into any
style you like
ohya, do remember to deploy to activate the trigger with event type
as On Change. That is not limiting to edit, but for all kinds of
changes including paste.
Here's a screenshot on how it would look like:
Lastly, please take note on some of my backgrounds before deciding to or not to have the solution to work for your case. Cheers, and happy coding~!
You cannot get a permanent timestamp with a spreadsheet formula, even with a named function or an Apps Script custom function, because formula results refreshed from time to time. When the formula gets recalculated, the original timestamp is lost.
The easiest way to insert the current date in a cell is to press Control + ; or ⌘;. See the keyboard shortcuts help page.
You can also use an onEdit(e) script to create permanent timestamps. Search this forum for [google-apps-script] timestamp to find many examples.

Dynamic Page Breaks in Google Sheets

I have mild general programming knowledge but know basically nothing about google apps scripts specifically.
I am trying to create dynamic page breaks in google sheets, or find another way to keep certain rows grouped together when printing.
On my data sheet I have 100s of rows of information and within each row the data can vary significantly in length (from a single number to many paragraphs of text). I have created a second sheet that both filters the information that I want and displays it in a visually-appealing way, taking the original data from each row and parsing it into 8 total rows (7 with information, and one blank to visually separate one block of info from the next) per one original. The problem is that the varying length of the data means I have to manually move the page breaks every time I change the filter.
Here is a blank section of the second sheet for reference.
I want to be able to print with as many 8-row groupings on a page as I can, but not split up a group onto the next page.
I'm honestly not sure how to get started, though I presume that I can use the blank row to trigger the page breaks somehow. Any help would be greatly appreciated!
Updated
I have been able to write some rudimentary code to (mostly) accomplish what I wanted. However the best that I can tell is that getRowHeight() is not working with my wrapped text, as it properly formats when I have any empty data set, but not otherwise.
Can someone confirm, and tell me what I'm missing?
function dynamicPageBreaks() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var curRow = 2; //start on Row 2 (1 contains the filter selection)
var curTotPix = ss.getRowHeight(curRow);
var pgPix = 1030;
//loop until end of sheet
var endRow = ss.getLastRow();
do {
//find row that goes past page break
do {
curRow++;
curTotPix = curTotPix + ss.getRowHeight(curRow);
} while (curTotPix <= pgPix);
//get value of cell in column B of that row
var curCell = sheet.getRange(curRow,2).getValue();
//back up until we find an empty row
if (curCell == "") {
break;
} else do {
curTotPix = curTotPix - ss.getRowHeight(curRow);
curRow = curRow - 1;
curCell = sheet.getRange(curRow,2).getValue();
} while (curCell != "");
//expand empty row to match necessary pixels
var addHeight = pgPix - curTotPix;
ss.setRowHeight(curRow - 1, ss.getRowHeight(curRow) + addHeight);
//reset for next iteration
curTotPix = ss.getRowHeight(curRow);
} while (curRow < endRow);
}
I would recommend you get started by following an Apps script quickstart like the one about extending sheets here[1]
Then you can define your logic using methods from the Spreadsheet service[2].
You have the method getRowHeight()[3] which could help you in determining the length of the range you are looking to print. And that also depends on the paper size you choose.
[1]https://developers.google.com/apps-script/guides/sheets#get_started
[2]https://developers.google.com/apps-script/reference/spreadsheet
[3]https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrowheightrowposition

Lag time in starting function from On Edit trigger - Google Apps Script

I have been using an On Edit trigger in a google apps script. While making this script, I have noticed that there is a short period of time between when a user finishes making an edit to the google sheet, and when the function called by the On Edit trigger begins to execute. Although this lag time is short, it is long enough for a user to quickly make additional edits to the sheet before running the function called by the On Edit trigger.
Is there any way to reduce/eliminate this lag time or to prevent the user from making any changes to the sheet while waiting for the function to begin?
I understand that the code within the function can cause a delay while the function is running, but I can resolve that issue by temporarily protecting the sheet while the function runs, and unprotecting it at the end of the function. This still leaves the issue with the lag time from the trigger because I can't protect the sheet until the function starts to run.
I can prove the existence of the lag time with the code below. The first thing this function does when called by the On Edit trigger is set the value of cell G1 to 69. If you delete the 69 in G1, you can quickly type in a different number and enter it before the function changes the value back to 69 again. The additional code and the other stuff on the sheet are just there to demonstrate what exactly I am trying to achieve with this and why. Here is a link to the related google sheet (it runs the function in the code block below when triggered by an edit): https://docs.google.com/spreadsheets/d/1BZB7YWguMdts-pvr7SSSEDUgSAOe023o9b_6VOhUoLw/edit?usp=sharing
// The goal of this function is to only allow the user to fill in column A with items from column D. The user should only be able to enter each item from column D once, but they can
// be moved around and replaced at will.
function triggerTest() {
//These two lines are just to quickly prove lag time. Nothing to do with the rest of this code...
let testCell = SpreadsheetApp.getActiveSheet().getRange('G1');
testCell.setValue(69);
//These next 4 lines are just putting the values in column A and column D into arrays (getDisplayValues() returns a 2d array)
//The cleanArray function deletes blank cells from the array and makes it a 1d array
let databaseValues = SpreadsheetApp.getActiveSheet().getRange('D2:D11').getDisplayValues();
let cleanDatabaseValues = cleanArray(databaseValues);
let itemsColumnValues = SpreadsheetApp.getActiveSheet().getRange('A2:A11').getDisplayValues();
let cleanItemsColumnValues = cleanArray(itemsColumnValues);
//These for loops find all items in the column D that are not already in column A and puts them into a new array - valuesInDatabaseAndNotInItemsColumn[]
let valuesInDatabaseAndNotInItemsColumn = cleanDatabaseValues;
for(let i=0; i<cleanItemsColumnValues.length; i++){
for(let j=0; j<valuesInDatabaseAndNotInItemsColumn.length; j++){
if(valuesInDatabaseAndNotInItemsColumn[j] === cleanItemsColumnValues[i]){
valuesInDatabaseAndNotInItemsColumn.splice(j,1);
j--;
}
}
}
//This is the data validation for column A. It only allows values that are in Database column and NOT already in the Items column. If there is a duplicate both entries are invalid.
//Since every cell undergoes validation, the cells that already contain entries must be allowed to keep them which is the reason for the .concat on line 44. Duplicate cells are not
//allowed to contain their current value, hence the absence of .concat on line 41.
for(let i=0; i<itemsColumnValues.length; i++){
let cellIndex = (i + 2).toString();
let cell = SpreadsheetApp.getActiveSheet().getRange('A'+ cellIndex);
let duplicate = false;
let cellValidation = SpreadsheetApp.newDataValidation();
for(let j=0; j<itemsColumnValues.length; j++){ //This for loop tests to see if there is a duplicate of the entry
if(j != i && itemsColumnValues[j][0] === itemsColumnValues[i][0]){
duplicate = true;
}
}
if(duplicate === true){
cellValidation.requireValueInList(valuesInDatabaseAndNotInItemsColumn, true).setAllowInvalid(false).build();
}
else{
cellValidation.requireValueInList(valuesInDatabaseAndNotInItemsColumn.concat(itemsColumnValues[i][0]), true).setAllowInvalid(false).build();
}
cell.setDataValidation(cellValidation);
}
}

Set formula for adjacent cell if text is present

I'm working with a Google Sheets form which also accepts answers via text message. I'm trying to work out a method using Google Apps Scripts to split the body of the text message using a comma as a delimiter.
The problem I'm running into is overwriting information submitted by the form and not by text message.
My current script is:
function splitCells() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var colC = sheet.getRange("C2:C").getValues();
var colD = sheet.getRange("D2:D").getFormulas();
//Logger.log(colC);
for(var i in colC){
if(typeof(colC[i][0]) =='string'){
colD = '=if(istext(C2:C),split(C2:C,",",true))';
} else {
colD = 'D2:D';
}
}
sheet.getRange("D2:D").setFormula(colD);
}
The function is working correctly, splitting the contents of column C (the SMS body) into D, E, and F as expected. But, it's overwriting data in column D because the else condition isn't being met (colC is blank in those places).
How do I get the script to move over blank cells without replacing the contents of the cell?
It's sort of confusing to explain, so here's a sample document you can check out. A custom menu should install when you open it and you can run the script from there (or from the editor).
Thanks for the help.
There are a few simple mistakes to start.
A spreadsheet cell can contain a value or a formula, not both.
If you use setFormula/s(), any value in a cell will be replaced by the result of the formula, even if the formula is blank.
Since you want to have a mix of values and formulas, you should set formulas only in the specific cells that match the criteria:
// If we received a SMS response, set a formula to parse it
sheet.getRange(2+i,4).setValue('=if(istext(C2:C),split(C2:C,",",true),"")')
The criteria test isn't sufficient. A blank cell is still of type string, but it's a blank string. So this evaluates true for both form entries and SMS entries:
if(typeof(colC[i][0]) =='string'){ ...
A more effective test checks for a non-blank response:
if(colC[i][0] != ''){ ...
An even better one would ensure that the value in column C meets the required format requirements.
You are looping over an array using the for .. in loop, which is meant for going over object properties. This works, but the loop value i will be a string, which can cause problems when doing math. Better to get in the habit of looping over the numeric index. (See.)
The full-column range expression C2:C is elegant, however you end up with an array that contains all rows in the spreadsheet, more than a thousand in your example. Since we're going to loop over all rows, it's best to limit that range:
var colC = sheet.getRange(2, 3, sheet.getLastRow()).getValues(); // C2:C, only non-blank rows
Adjusting for those problems, we have:
function splitCells2() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var colC = sheet.getRange(2, 3, sheet.getLastRow()).getValues(); // C2:C, only non-blank rows
//Logger.log(colC);
for(var i=0; i< colC.length; i++){
if(colC[i][0] != ''){
// If we received a SMS response, set a formula to parse it
sheet.getRange(2+i,4).setValue('=if(istext(C2:C),split(C2:C,",",true),"")')
}
}
}

Script to permute columns, rows or any ranges

EDIT: I changed the code to include possibility of providing ranges by name (in A1 notation) as this could be potentially more efficient than providing Range object (if the range ends up not moved) and for sure is easier to use in simple cases. Idea by AdamL (see answers bellow).
In some spreadsheets I need to permute rows or columns. Requiring user to do this manually isn't very nice. So making proper commands in menu which would run script seemed a reasonable solution.
Oddly I wasn't able to find any function (either build in or wrote by someone else) which would permute rows/columns. So I wrote one myself and then considered publishing it. But since my experience with JavaScript and Google Apps Script is low I wanted to have someone else check on this function. Also I have some questions. So here we go.
// Parameters:
// - ranges An Array with ranges which contents are to be permuted.
// All the ranges must have the same size. They do not have to be
// vectors (rows or columns) and can be of any size. They may come from
// different sheets.
// Every element of the array must be either a Range object or a string
// naming the range in A1 notation (with or without sheet name).
// - permutation An Array with 0-based indexes determining desired permutation
// of the ranges. i-th element of this array says to which range
// should the contents of i-th range be moved.
// - temp A range of the same size as the ranges in "ranges". It is used to
// temporarily store some ranges while permuting them. Thus the initial
// contents of this range will be overwritten and its contents on exit is
// unspecified. Yet if there is nothing to be moved ("ranges" has less
// than 2 elements or all ranges are already on their proper places) this
// range will not be used at all.
// It is advised to make this range hidden so the "garbage" doesn't
// bother user.
// This can be either a Range object or a string naming the range in A1
// notation (with or without sheet name) - just as with the "ranges".
// - sheet An optional Sheet object used to resolve range names without sheet
// name. If none is provided active sheet is used. Note however that it
// may cause issues if user changes the active sheet while the script is
// running. Thus if you specify ranges by name without sheet names you
// should provide this argument.
//
// Return Value:
// None.
//
// This function aims at minimizing moves of the ranges. It does at most n+m
// moves where n is the number of permuted ranges while m is the number of
// cycles within the permutation. For n > 0 m is at least 1 and at most n. Yet
// trivial 1-element cycles are handled without any moving (as there is nothing
// to be moved) so m is at most floor(n/2).
//
// For example to shift columns A, B and C by 1 in a cycle (with a temp in
// column D) do following:
//
// permuteRanges(
// ["A1:A", "B1:B", "C1:C"],
// [1, 2, 0],
// "D1:D",
// SpreadsheetApp.getActiveSheet()
// );
function permuteRanges(ranges, permutation, temp, sheet) {
// indexes[i] says which range (index of ranges element) should be moved to
// i-th position.
var indexes = new Array(permutation.length);
for(var i = 0; i < permutation.length; ++i)
indexes[permutation[i]] = i;
// Generating the above array is linear in time and requires creation of a
// separate array.
// Yet this allows us to save on moving ranges by moving most of them to their
// final location with only one operation. (We need only one additional move
// to a temporary location per each non-trivial cycle.)
// Range extraction infrastructure.
// This is used to store reference sheet once it will be needed (if it will be
// needed). The reference sheet is used to resolve ranges provided by string
// rather than by Range object.
var realSheet;
// This is used to store Range objects extracted from "ranges" on
// corresponding indexes. It is also used to store Range object corresponding
// to "temp" (on string index named "temp").
var realRanges;
// Auxiliary function which for given index obtains a Range object
// corresponding to ranges[index] (or to temp if index is "temp").
// This allows us to be more flexible with what can be provided as a range. So
// we accept both direct Range objects and strings which are interpreted as
// range names in A1 notation (for the Sheet.getRange function).
function getRealRange(index) {
// If realRanges wasn't yet created (this must be the first call to this
// function then) create it.
if(!realRanges) {
realRanges = new Array(ranges.length);
}
// If we haven't yet obtained the Range do it now.
if(!realRanges[index]) {
var range;
// Obtain provided range depending on whether index is "temp" or an index.
var providedRange;
if(index === "temp") {
providedRange = temp;
} else {
providedRange = ranges[index];
}
// If corresponding "ranges" element is a string we have to obtain the
// range from a Sheet...
if(typeof providedRange === "string") {
// ...so we have to first get the Sheet itself...
if(!realSheet) {
// ...if none was provided by the caller get currently active one. Yet
// note that we do this only once.
if(!sheet) {
realSheet = SpreadsheetApp.getActiveSheet();
} else {
realSheet = sheet;
}
}
range = realSheet.getRange(providedRange);
} else {
// But if the corresponding "ranges" element is not a string then assume
// it is a Range object and use it directly.
range = providedRange;
}
// Store the Range for future use. Each range is used twice (first as a
// source and then as a target) except the temp range which is used twice
// per cycle.
realRanges[index] = range;
}
// We already have the expected Range so just return it.
return realRanges[index];
}
// Now finally move the ranges.
for(var i = 0; i < ranges.length; ++i) {
// If the range is already on its place (because it was from the start or we
// already moved it in some previous cycle) then don't do anything.
// Checking this should save us a lot trouble since after all we are moving
// ranges in a spreadsheet, not just swapping integers.
if(indexes[i] == i) {
continue;
}
// Now we will deal with (non-trivial) cycle of which the first element is
// i-th. We will move the i-th range to temp. Then we will move the range
// which must go on the (now empty) i-th position. And iterate the process
// until we reach end of the cycle by getting to position on which the i-th
// range (now in temp) should be moved.
// Each time we move a range we mark it in indexes (by writing n on n-th
// index) so that if the outer for loop reaches that index it will not do
// anything more with it.
getRealRange(i).moveTo(getRealRange("temp"));
var j = i;
while(indexes[j] != i) {
getRealRange(indexes[j]).moveTo(getRealRange(j));
// Swap index[j] and j itself.
var old = indexes[j];
indexes[j] = j;
j = old;
}
getRealRange("temp").moveTo(getRealRange(j));
// No need to swap since j will not be used anymore. Just write to indexes.
indexes[j] = j;
}
}
The questions are:
Is this properly implemented? Can it be improved?
How about parameters validation? Should I do it? What should I do if they are invalid?
I wasn't sure whether to use copyTo or moveTo. I decided on moveTo as it seemed to me more what I intended to do. But now in second thoughts I think that maybe copyTo would be more efficient.
Also I noticed that the Range moved from not always is cleared. Especially when in Debugger.
Undo/redo seems to be an issue with this function. It seems that every moveTo is a separate operation (or even worse, but maybe that was just a low responsiveness of the Google Docs when I was testing) on the spreadsheet and undoing the permutation is not a single action. Can anything be done about it?
The documentation I wrote for the function claims that it works across different sheets or even different spreadsheets. I haven't actually checked that ;) but Google Apps Script documentation doesn't seem to deny it. Will it work that way?
I'm not sure whether this is a proper place to ask such questions (since this is not truly a question) but since Google Apps Script community support is moving to Stack Overflow I didn't knew where else to ask.
Don't you think it might be more efficient in terms of execution speed to do it with arrays ?
try this for example : (I added logs everywhere to show what happens)
(Note also that sheets are limited to 255 columns... take care of the list length)
function permutation() {
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var lr = ss.getLastRow()
var lc=ss.getLastColumn();
var data = sh.getRange(1,1,lr,lc).getValues()
Logger.log(data)
var temp2= new Array();
var h=data.length
Logger.log(h)
var w=data[0].length
Logger.log(w)
for(nn=0;nn<w;++nn){
var temp1= new Array();
for (tt=0;tt<h;++tt){
temp1.push(data[tt][nn])
}
temp2.push(temp1)
}
Logger.log(temp2)
Logger.log(temp2.length)
Logger.log(temp2[0].length)
sh.getRange(1,1,lr,lc).clear()
sh.getRange(1,1,lc,lr).setValues(temp2)
}
best regards,
Serge
Adam, from my limited experience on the Apps Script GPF, I have learned that it is best to limit get and set calls as much as possible (and you could include moveTo/copyTo in that as well).
Do you think it would be better to pass the range names, rather than the ranges, as parameters (and to that end, you might need a mechanism to pass sheet names and spreadsheet keys as well, to support your requirement of working across different sheets/spreadsheets), and then trivial "getRange's" can be avoided as well as a trivial "moveTo's".
Also, if you are just transferring values only, it would probably be better to not move it to a temporary range but rather assign those arrays to a variable in the script which can then be later "set" in the correct spot. But if you need to copy over formats or formulae, that's a different story.