Add value to each cell in range (Google apps script) - javascript

Im building a macro for google sheets, in which i iterate through a range and add a value to each cell. I've been trying a few different ways to do it, but they are all really slow. For 30 cells, this takes about a minute. Is there any way to make it faster/write it in a different way? This is a macro that will be run a lot of times in a day, for ranges of about 30 columns max.
for (var j = 1; j <= range.getNumColumns(); j++) {
if(range.getCell(1, j).getValue() == "") {
range.getCell(1,j).setValue(valueToAdd/(colEnd-colStart));
} else range.getCell(1,j).setValue(valueToAdd/(colEnd-colStart)+(parseFloat(range.getCell(1,j).getValue())));
}

Try it this way:
function iterateThroughARange() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Sheet1');
const rg=sh.getDataRange();
const vs=rg.getValues();
vs.forEach(function(r,i){
r.forEach(function(c,j){
vs[i][j]='Your Adjustment';
});
})
rg.setValues(vs);
}
Best Practices
This way you get all of the data at one time perform you adjustment and save it all at one time. It get's trickier if you want to avoid writing in certain cells. Then you may have to go back to the slower method of writing to individual cells. So Thinking about how how layout a spreads can impact the speed of your scripts quite a bit.

Related

Google sheet cell is only writen once instead of multiple times

I would like to make a visual random number genarator. That means in Cell F4 should random numbers shuffle trough until it stops with a random number.
My Problem is now, that i can only see the last number generated and not the shuffeling.
function Wuerfeln() {
var Upperbound = 60;
var Lowerbound = 1;
var i = Math.floor(Math.random() * (50 - 10 + 1) + 10);
var RandomNummber = 0;
var range = SpreadsheetApp.getActiveSpreadsheet().getRange("F4");
while(i>0)
{
RandomNummber = Math.floor(Math.random() * (Upperbound - Lowerbound + 1) + Lowerbound);
range.setValue(RandomNummber);
i--;
Utilities.sleep(200);
};
};
Google Sheets has a minimum refresh time, you're not going to make it change super fast by design.
Many requests from a script get batched together and run at the same time, so when the edits are all in the same cell you do not see them change and only the last one is displayed.
The only way to force a cell to change is using SpreadsheetApp.flush() as suggested by Sergey. This forces all pending sheet changes to be pushed. As you have noted however, this is slow.
To do this at all you would need to have an HTML dialog or sidebar which can run JavaScript that directly changes the HTML elements rather than a cell in the spreadsheet.
I would suggest you define a few things and then try it out until it looks like you want, or what you could do is create a pop up which shows a .gif of a dice.
The first approach would look like this
function wurfel(){
const timeDelay = 200
const numbersToShowUntilFinal = 20 // or do a random num
// then your code here,
// but using the variables above to test around until you found a cool
}

How to automatically fill table based on certain rules (with JS)

I have shift planning table created by JS and shown to user as html. This table is currently filled by user. I have certain set of rules and when user breaks them, he gets warning and he have to fix them.
I want to create script which fills this table automatically. I know how to do this, but i don't know how to do it so that script obeys all that rules and preferences.
For example. I cant give to all users all morning shifts. I need to have it so that one person have no more that 5 shifts in on week, have at least one free weekend, have no more than 20 shifts per month and have them layed out equaly all over the month (not all shifts at the start)
Is this even possible with javascript? If yes could you please give me some idea how?
My current idea is to use 'IF' to obey the rules, but i need to have some rules on same level, so if i use 'IF' and logical operators, but i am kinda stuck on it for days and i am not certain that this is right way to go.
Fiddle with layout of table
Basic draft of my idea to fill table by rules:
function autoFill() {
var numberOfPeople = people.length;
var days = daysInThisMonth();
var ruleOne = ruleOneFunction(); //boolean
var ruleTwo = ruleTwoFunction(); //boolean
for(var i = 0; i < numberOfPeople; i++) {
for(var j = 1; j <= days; j++) {
if(ruleOne === true && ruleTwo === true) {
//fill table here
}
}
}
};

What would be a proper Google Script for copying dynamically updating range and pasting in Archive sheet?

I am new to Google App scripting, and I have no prior knowledge of scripting of any type other than basic HTML. However, Google App script didn't pose much of a challenge thanks to this forum.
I am a data analyst, and has been researching on low-cost/open source ways to emulate some of basic Big Data advantages for website publishers. My quest brought me to Google scripts. I have been able to write a few since I got to know about it a week ago.
The objective challenge is:
I have a spreadsheet that dynamically pulls about 1000 rows using IMPORTHTML function. The range automatically refreshes as the source refreshes everyday, so previous day's data is lost. That calls for backing up the data in an archive sheet, so that I can analyze the historical data on time-ranges of choice.
I want to automatically copy the rows and paste them on the top of the archive sheet, just below the range header, so that I don't have to sort the archive by dates, which may be required for data analysis. I also need to check for duplicate rows and remove them--just in case.
First I wrote a script that appended the copied rows below the last row of the archived range. However, sorting by date became necessary, as I had to filter the data by specific date ranges, say 14 days or 7 days, for advanced analysis. So I added a snippet for sorting and another for removing duplicates. It works well, however, sorting takes a long time. And considering thousands of new rows being added everyday, it will continue to take longer. I needed a smarter solution.
So I started writing a script that will (1) detect the number of rows in the source range (2) Insert as many rows below the header of the archive sheet and (3) paste copied range into the newly inserted rows.
I finished writing it, and it works very fast; apparently no sorting is required. However, I was wondering, if there is a way to make it even quicker and smarter and future-proof. Please find the code below. Any suggestion will be highly appreciated.
function myFunction() {
//1. Get data from source sheet of a spreadsheet whose id is known, we will also need the data range's last row number
var firstStep = SpreadsheetApp.openById("ID of Source Spreadsheet");
var ss = firstStep.getSheetByName("Sheet1");
ss.activate();
var myRange = ss.getRange(4, 2, ss.getLastRow() - 3, ss.getLastColumn());
var myData = myRange.getValues();
//'3' subtracted from last row data collector above as first three rows contain static data or blank row in my source sheet. Applied same technique at line 17 below as well. This totally depends on how you position the source range in the source sheet. For exaple, for a range starting at 1,1 on any sheet, no such subtraction woud be required.
var lastRow = myRange.getLastRow() - 3;
//2. Open archive spreadsheet, select the destination sheet, insert exact number of rows of source range and then paste copied range.
var secondStep = SpreadsheetApp.openById("ID of archive spreadsheet");
var newSS = secondStep.getSheetByName("dump1");
newSS.activate();
//2.a Insert Rows as in #lastrow in the new sheet, just below the header at Row 1
newSS.insertRowsBefore(2, lastRow)
//2.b Paste values
newSS.getRange(2, 1, myData.length, myData[0].length).setValues(myData);
//2.c Paste last row number of the copied range in another cell of the same sheet, optional step, just to be sure that last row determination process is right. You may remove this step if you like.
newSS.getRange(1, 15).setValue(lastRow);
/*
//3.a Optional: Script to remove duplicate rows in archive sheet. Will increase the script-run duration considerably.
var data = newSS.getDataRange().getValues();
var newData = new Array();
for(i in data){
var row = data[i];
var duplicate = false;
for(j in newData){
if(row.join() == newData[j].join()){
duplicate = true;
}
}
if(!duplicate){
newData.push(row);
}
}
newSS.clearContents();
newSS.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
*/
}
Anything you can accomplish within Google apps Script itself will be much faster than making calls that need to fetch data from Google's servers or an external server such as requests to Spreadsheets, Docs, sites and so on. Your scripts will run faster if you can find ways to minimize the calls the scripts make to those services.
To speed up a script, read all data into an array with one command, perform any operations on the data in the array and write the data out with one command.
Here's an example:
var cell = sheet.getRange('a1');
var colors = new Array(100);
for (var y = 0; y < 100; y++) {
xcoord = xmin;
colors[y] = new Array(100);
for (var x = 0; x < 100; x++) {
colors[y][x] = getColor_(xcoord, ycoord);
xcoord += xincrement;
}
ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
You must use the Google's Best Practice, The highlight from Google's List are:
Reduce the number of API calls
When making API calls, batch the requests
Use the Apps Script built in cache service
Do not use UIApp; use HTMLService
Here's a document list best practices that will help you improve the performance of your scripts: https://developers.google.com/apps-script/best_practices#minimize-calls-to-other-services

How to split an array into two subsets and keep sum of sub-values of array as equal as possible

I really need an master of algorithm here! So the thing is I got for example an array like this:
[
[870, 23]
[970, 78]
[110, 50]
]
and I want to split it up, so that it looks like this:
// first array
[
[970, 78]
]
// second array
[
[870, 23]
[110, 50]
]
so now, why do I want it too look like this?
Because I want to keep the sum of sub values as equal as possible. So 970 is about 870 + 110 and 78 is about 23 + 50.
So in this case it's very easy because if you would just split them and only look at the first sub-value it will already be correct but I want to check both and keep them as equal as possible, so that it'll also work with an array which got 100 sub-arrays! So if anyone can tell me the algorithm with which I can program this it would be really great!
Scales:
~1000 elements (sublists) in the array
Elements are integers up to 10^9
I am looking for a "close enough solution" - it does not have to be the exact optimal solution.
First, as already established - the problem is NP-Hard, with a reduction form Partition Problem.
Reduction:
Given an instance of partition problem, create lists of size 1 each. The result will be this problem exactly.
Conclusion from the above:
This problem is NP-Hard, and there is no known polynomial solution.
Second, Any exponential and pseudo polynomial solutions will take just too long to work, due to the scale of the problem.
Third, It leaves us with heuristics and approximation algorithms.
I suggest the following approach:
Normalize the scales of the sublists, so all the elements will be in the same scale (say, all will be normalzied to range [-1,1] or all will be normalized to standard normal distribution).
Create a new list, in which, each element will be the sum of the matching sublist in the normalized list.
Use some approximation or heuristical solution that was developed for the subset-sum / partition problem.
The result will not be optimal, but optimal is really unattanable here.
From what I gather from the discussion under the original post, you're not searching for a single splitting point, but rather you want to distribute all pairs among two sets, such that the sums in each of the two sets are approximately equal.
Since a close enough solution is acceptable, maybe you could try an approach based on simulated annealing?
(see http://en.wikipedia.org/wiki/Simulated_annealing)
In short, the idea is that you start out by randomly assigning each pair to either the Left or the Right set.
Next, you generate a new state by either
a) moving a randomly selected pair from the Left to the Right set,
b) moving a randomly selected pair
from the Right to the Left set, or
c) doing both.
Next, determine if this new state is better or worse than the current state. If it is better, use it.
If it is worse, take it only if it is accepted by the acceptance probability function, which is a function
that initially allows worse states to be used, but favours them less and less as time moves on (or the "temperature decreases", in SA terms).
After a large number of iterations (say 100.000), you should have a pretty good result.
Optionally, rerun this algorithm multiple times because it may get stuck in local optima (although the acceptance probability function attempts to counter this).
Advantages of this approach are that it's simple to implement, and you can decide for yourself how long
you want it to continue searching for a better solution.
I'm assuming that we're just looking for a place in the middle of the array to split it into its first and second part.
It seems like a linear algorithm could do this. Something like this in JavaScript.
arrayLength = 2;
tolerance = 10;
// Initialize the two sums.
firstSum = [];
secondSum = [];
for (j = 0; j < arrayLength; j++)
{
firstSum[j] = 0;
secondSum[j] = 0;
for (i = 0; i < arrays.length; i++)
{
secondSum += arrays[i][j];
}
}
// Try splitting at every place in "arrays".
// Try to get the sums as close as possible.
for (i = 0; i < arrays.length; i++)
{
goodEnough = true;
for (j = 0; j < arrayLength; j++)
{
if (Math.abs(firstSum[j] - secondSum[j]) > tolerance)
goodEnough = false;
}
if (goodEnough)
{
alert("split before index " + i);
break;
}
// Update the sums for the new position.
for (j = 0; j < arrayLength; j++)
{
firstSum[j] += arrays[i][j];
secondSum[j] -= arrays[i][j];
}
}
Thanks for all the answers, the bruteforce attack was a good idea and NP-Hard is related to this too, but it turns out that this is a multiple knapsack problem and can be solved using this pdf document.

Internet Explorer Javascript performance problem

JavaScript performance in Internet Explorer sucks. No news there. However there are some tips and tricks to speed it up. For example, there is this three part series. Still I find myself unable to squeeze decent performance out of it. Perhaps some of you have an idea what else to do so it was speedier?
What I want to do is create a medium-size table from scratch in Javascript. Say, 300 rows, 10 cells each. It takes at about 5-6 seconds on my computer to do this. OK, granted, it's a 5 year old rig, but that's still too much. Here's my dummy code:
<html>
<body>
<script type="text/javascript">
function MakeTable(parent)
{
var i, j;
var table = document.createElement('table');
var insertRow = table.insertRow;
for ( i = 0; i < 300; i++ )
{
var row = insertRow(-1);
for ( j = 0; j < 10; j++ )
{
var cell = row.insertCell(-1);
cell.innerHTML = i + ' - ' + j;
}
}
parent.appendChild(table);
}
</script>
<div onclick="MakeTable(this);">Click Me!</div>
</body>
</html>
Added: Hmm, apparently string-concatenation (with array.join) is the only way to go. Well, sad, of course. Was hoping to do it the "proper" DOM-way. :)
Here is an interesting link I found when looking for an answer on this:
The page uses five different scripts / methods to generate a table.
According to their tests, using strings is by far faster than using DOM / Table elements.
http://www.quirksmode.org/dom/innerhtml.html
One of the main reason's for IE's performance issues are DOM operations. You want to do your DOM operations as efficiently as possible. This can include, depending on your situation (benchmark!):
Offline creation of your DOM structure; keep the top level element out of the document (create, but not append) then appending it to the document when it's ready, instead of appending every element into the DOM as you create it
write innerHTML instead of DOM manipulation
You could try 'Duff's Device': Unwinding a loop by repeating the code a number of times:
for (var i = 0; i < count / 4; i++) {
doSomething();
doSomething();
doSomething();
doSomething();
}
Obviously this leaves the remainder when divided by 4, the original Duff's Device had a clever way of jumping to the middle of the loop using a switch statement mixed in with the loop. Javascript does not support this, but you could manually process the rest of your rows. Also the number 4 is random, the number itself can be derived by performance testing.
See also: http://www.websiteoptimization.com/speed/10/10-3.html

Categories

Resources