I have an onEdit-script which calculates the value of a cell based on the content of a row of other cells.
I need this script to be in an onEdit-trigger rather than a regular cell-function because I don't always want the calculation to be redone when the value in one of the targeted cells is changed, but instead it checks for certain conditions and recalculates only when those are met.
A small problem I'm having with this is that one of the cells that the onEdit-script reads data from contains a function rather than a simple number.
This causes a problem because sometimes when I enter data in a cell, it will trigger both this cell-function and the onEdit-script. And most of the time the oEdit-function runs before the cell-function finishes so the onEdit-function just picks up "Thinking" from this cell and thus it returns NaN.
So I guess a convenient solution to this would have been to make the onEdit-function wait for the "targeted" cells to finish their calculation but I don't think there is a way to do this?
Of course I could move the cell-function(which basically is a SUM-function with some added functionality) to the onEdit-script, which would solve the issue.
But to me it doesn't seem so nice having all interactivity in the onEdit-trigger. Or am I just being silly?
Or is there another approach I could take somehow?
I think the best solution would be to replace your custom formula (that "thinks") with a regular spreadsheet formula if possible. And it seems to be your case, since it's just a SUM function with "added functionality". Probably a SUMIF will suffice.
If your function is indeed complicated and can't be written as regular formula (which I really doubt), the best solution would be indeed to move the calculation to the onEdit-trigger.
Regardless of your problem ,I always advise to not use custom formulas, they are really problematic. e.g. haven't you ran into the caching issue?
You might try:
SpreadsheetApp.flush();
at the beginning of your onEdit() script to force recalc before continuing.
http://code.google.com/googleapps/appsscript/class_spreadsheetapp.html#flush
To get onEdit to wait for the calculations, you might try:
function onEdit(e) {
// ...some if-condition that makes sure the following sleep is not called whenever any cell in your spreadsheet is edited
Utilities.sleep(1000); // 1000 is the amount of milliseconds
}
https://developers.google.com/apps-script/reference/utilities/utilities#sleep(Integer)
Although using sleep is brittle, in case the calculations take more or less time than you anticipated. It can introduce timing issues which are hard to debug.
So I guess a convenient solution to this would have been to make the onEdit-function wait for the "targeted" cells to finish their calculation but I don't think there is a way to do this?
To have the aforementioned if-condition reference targeted cells (a specific range), see these answers: Google Spreadsheet SCRIPT Check if edited cell is in a specific range
Related
My situation is pretty simple. I'm using lockservice to avoid concurrent update of one row by different instances of same script, like this:
LockService.getScriptLock().waitLock(5000);
if(rowIndex > 1) {
row = sheet.loadRowAt(rowIndex);
sheet.saveRowAt(_.assign(row, newValues), rowIndex);
} else {
sheet.appendRow(newValues)
}
SpreadsheetApp.flush();
LockService.getScriptLock().releaseLock();
(sheet is my own obj, not internal Sheet so don't worry for unknown methods)
The question is - do I need a line with SpreadsheetApp.flush() to make sure changes by this section would be immediately available to parralel script run, which might already be waiting for the lock.
Apparently, I want to keep the code inside lock as quick as possible, so if this line is redundant, I want to get rid of it.
To re-phrase the question, does the SpreadsheetApp.flush() actually affects sheet values in memory OR it only relates to display of them to end-user currently observing the sheet and I can forget about it until I don't care about end-user display delay?
I am writing a math paper where i would like to display my calculations (formulas) separately from the solution.
I am currently working in Google Sheets.
The end goal would be to have one column with formulas and one column with answers.
I tried to work with GS to write a function that would take the string value from A1 and evaluate it in the B1 column.
I used this simple script that i found on:
https://support.google.com/docs/thread/13826624/evaluate-string-as-formula?hl=en
function run(input){
return eval(input);
}
It works with simple calculations like division, multiplication, addition and subtraction.
But the script doesn't solve basic exponents like 1 * 10^3 (it gives me 9). And square roots like sqrt(9) (gives me an #error)
I'm not sure which way to go from here.
The easiest solution would probably be to work the other way around: write your formulas normally, and use the formulatext() spreadsheet function to display the formula in an adjacent cell.
The lecture for my Java class has this piece of code:
for (int i=0; i<arr.length; i=i+10){
if(i%10 == 0){
System.out.println(arr[i]);
}
}
If you start at 0 and then go 10, 20, etc. Why do you need the if condition? Naturally all of these numbers divide by 10.
It's redundant. The only way it could have an effect is when the array length is close to the Integer max value and you're causing overflows by adding 10, but then your code would loop infinitely anyway (or crash when accessing negative array values).
To me the code in the if condition might have 2 reasones:
It is a way to monitor the progress of the function (although since the condition of the for loop is i=i+10 instead of i++, it is less meaningful in this case). This is very normal when we are using some script to execute a task that is dealing with a lots of data (normally in single process, and take some time). By printing out the progress periodically we are able to know (or estimate) how many data has been read/wrtie, or how many times have the codes in the loop has been executed, in this case.
There might be more code added in the for loop, which might modify i. In this case, i%10 == 0 will be meaningful.
In other words, without any more context it does seems like the if condition is redundant, in this case.
To answer the question of the title, here's what we usually do. First, have the code review done by someone else before you merge your branch. Having another fellow to review your codes are good practise as they could give you a fresh mind on correctness and code style. Second, if you find something that is suspecious but not sure (for example, the "redundant code" you think here), wrote unit tests to cover the part of code that you would like to change, make the changes and rerun the unit tests and see if you still get what is expected.
Personally I haven't heard of any tools that is able to detect "redundant code" as the example here, as "redundant" might not be "redundant" at all under different circumstances.
Is it possible to apply the 'Automatic' number format programmatically through GAS? My issue is that as I write columns of numbers, Sheets seems to attempt to apply appropriate formatting, but gets it wrong sometimes. That is, particular small integers (1 sometimes) will be formatted as dates. The range is being written in one myRange.setValues() method and I can't see any pattern to the mistakes and therefore don't see any way to prevent the surprise mis-formatting.
But, when I select the range in sheets and just click "Automatic" on the number format menu all returns to normal. It doesn't help to click that upfront as the writing of data somehow resets the format.
Despite the long-winded intro, my question is very simple: how to programmatically apply "Automatic" number formatting. I'm thinking this is very basic, especially since google and searches here have been no help.
My current fallback solution is to use myRange.setNumberFormat("0") as the format for the whole range. This is not ideal as some numbers are very large and are easier to read in scientific notation. There are also some text strings in the range, but these format properly regardless of format applied. I also would prefer to avoid having to iterate through the data and test for values to determine the best format, when it's just a couple clicks in the user interface.
we can use .setNumberFormat('General');
Below is the example:
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange("B:B").setNumberFormat("General");
I use copyFormatToRange to copy/apply Automatic format:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var source_cell = sheet.getRange("A1");//A1: cell having automatic format
source_cell.copyFormatToRange(sheet,1,1,2,2);//copy format of cell A1 to cell A2
You can write an API that opens another spreadsheet, read any cell that having the automatic format.
var ss = SpreadsheetApp.openById(SpreadsheetId);//Id of another spreadsheet
Then use copyFormatToRange to your wanted cell.
I was having trouble finding anything documented, and tried pretty much everything suggested previously (null, 'General', the "magic" format of '0.###############', etc., etc.).
In my particular case, I had ranges previously set to strict plain text, which then got replaced with a checkbox data validation. Anytime the box was checked it was converted to the text "TRUE" instead of remaining a checkbox. 'General' and the "magic" format functionally worked fine, but did not actually set the format back explicitly to "Automatic".
I finally decided, why not just try this:
range.setNumberFormat('Automatic');
And it worked. This really should be documented, but at least a little bit of common sense lead me to the answer regardless.
If you don't have dates in the range, the below solution appears to be the best available option (without resorting to an API-based solution):
myRange.setNumberFormat('0.###############');
A zero-point-15x'#' seems to be a 'magic' number format that will allow very large numbers to show as scientific notation and smaller integers and decimals to show in the 'standard' format pre-application of number formatting. This is also the format that is returned for cells that contain non-dates formatted with the 'Automatic' selection in the user interface.
Adding or removing even one # will 'break the spell' and cause very large numbers to display in non-scientific notation. I also tested changes before the decimal place, but leaving the 15x#:
Also functional: myRange.setNumberFormat('#,##0.###############');
So there is some flexibility for prefixes.
Non-functional: myRange.setNumberFormat('#.###############');
The 0 is evidently required.
And finally,
Non-functional: savegameRange.setNumberFormat('0.##############[red]');
This turns numbers red, but breaks the 'magic' formatting. So no suffixes it appears.
Again, if you have dates in the range, this will not work as they will, not surprisingly, display as the underlying number. And potentially more problematic (but totally understandable), the only way to return them to date form is manually applying a date format, assuming you know which cells 'were' dates.
Complete replication of 'Automatic' number formatting requires traversing the range to find dates and apply desired date format, but otherwise applying the 'magic' format. (My original dataset was a mix of numbers and strings, so the simple approach given above works.)
I'm using Google Spreadsheet for my tax administration. All financial transactions that I have done in a year are in the spreadsheet. Because the tax rules in my country are quite complex, I've developed a number of JavaScript functions to help me with my calculations. There are many rows in my spreadsheet, about 1000. Each row has multiple references to those JavaScript functions.
This system worked beautifully before, but today I've found that Google installed some kind of runtime limiting system into Google Spreadsheet, causing many of my columns to abort with the following error:
Service invoked too many times in a short time: exec maxSimultaneous.
Try Utilities.sleep(1000) between calls.
After some investigation, it would appear that this time limit is there to protect against scripts that take too long time to run. My case is different: my scripts are all short, O(1) algorithms that do nothing but calculating some numbers. A typical script looks like this:
// Calculates the total amount excluding Value Added Tax, given
// an amount including Value Added Tax, and other sorts of information.
function ex_btw(inc_btw, commentaar, soort, btw_verlegd, btw_pct) {
Utilities.sleep(1000);
var soort = soort.toLowerCase();
if (soort == 'eten en drinken'
|| commentaar.match(/treinkaartje/i)
|| commentaar.match(/treinticket/i)
|| commentaar.match(/taxi/i)
|| commentaar.match(/ boek /i))
{
return inc_btw / 1.06;
} else if (soort == 'priveonttrekking'
|| soort == 'boete'
|| soort == 'belasting'
|| commentaar.match(/postzegel/i)
|| btw_verlegd == 'Ja')
{
return inc_btw;
} else {
return inc_btw / (1 + btw_pct);
}
}
The script is then invoked like this from a cell:
=IF(B6<>""; ex_btw(B6;D6;E6;J6;S6); "")
Maybe my problem is that I have too many script calls. Every single row calls about 6 of such scripts, so with 1000 rows I call 6000 times per spreadsheet.
How do I solve this problem? Is there a way to increase the execution limit, or is there a way to make the scripts run slower so that they don't hit the execution limit? As you can see in the example code I've already tried inserting Utilities.sleep(1000), but that doesn't seem to solve the problem. I don't care whether the scripts run slowly, I just just care that they finish without errors.
Can I pay to have the limit increased? I need to hand in my taxes in a few days.
Other alternatives that I've considered, but that are not feasible.
Using non-JavaScript functions. Not feasible because: they don't support collaboration like Google Spreadsheet does. I regularly go over the spreadsheet with a colleague to check whether we've made any mistakes. It helps that the both of us can immediately see any changes the other makes.
Have one huge-ass JavaScript function that iterates over rows and populates cells. Not feasible because:
Too error prone, it's very easy to make mistakes compared to my current method.
Does not update cells automatically until I re-run the script. I want to see any calculations immediately after I update other cells, just like a spreadsheet is supposed to do.
Using other spreadsheets like Excel and OpenOffice Calc. Not feasible because: they don't appear to offer the same scripting capabilities.
Writing my own financing app. Not feasible because: it takes too much take, it's not my core business, and tax rules change almost every year so I will have to constantly update the app. I can update a spreadsheet very quickly, but writing a financing app takes too much time.
I solved it by making every function sleep for a random period, like this:
Utilities.sleep(Math.random() * 5000);
It is important that the sleeping time is random, not constant. Apparently Google limits the maximum number of functions that may simultaneously be using CPU.
an alternative to the custom function might be to have an onEdit function trigger and then process either just the entered data or the whole column of numbers and place the results of the function in the target cell(s) as a number.
might be quicker