How to batch the calls in google app script - google-apps-script

I was trying to optimize runtime some code that ran really slowly, when searching it up google came up with this: https://developers.google.com/apps-script/guides/support/best-practices#:~:text=Use%20batch%20operations,-Scripts%20commonly%20need&text=Alternating%20read%20and%20write%20commands,data%20out%20with%20one%20command.
it shows an example of inefficient code:
var cell = sheet.getRange('a1');
for (var y = 0; y < 100; y++) {
xcoord = xmin;
for (var x = 0; x < 100; x++) {
var c = getColorFromCoordinates(xcoord, ycoord);
cell.offset(y, x).setBackgroundColor(c);
xcoord += xincrement;
}
ycoord -= yincrement;
SpreadsheetApp.flush();
}
and efficient code:
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] = getColorFromCoordinates(xcoord, ycoord);
xcoord += xincrement;
}
ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
I tried to understand how this code works and tried running it but first of all "cell" doesn't seem to get used and I do not understand the code at all. What is a version of the code that actually works and how does this make it more efficient? And what part of this code batches the calls and how can I use this in my own coding?

Basically, what it does it reduce the number of calls by its methods.
The inefficient code above calls offset and setBackgroundColor every loop thus making it resource intensive and time consuming.
For the efficient code, it uses the loop to build the 2d array which then will be used on a single call on the method setBackgroundColors to execute it by bulk.
What takes much of the run time is the method calls, so reducing it would be very beneficial. This would make use of loops to build 2d arrays for the array versions of the methods single execution.
e.g.:
setValue -> setValues
setBackgroundColor -> setBackgroundColors
Pseudocode:
from:
loop {
setValue(x[i][j])
}
to:
loop {
x[i][j] = data
}
setValues(x)

Related

Make 2d array from single array

I created a level editor for web game, I can build, save, load and play levels.
Now I want to edit some levels but I have a weird situation.
I export a level as a single array, it looks like this 3,4,5,5,7,89,4,2,1...and those numbers represent frames. (tile-based).
Now if I want to edit this level and save it again, I need a level to be described as multidimensional array.
Actually, when I save the level I have a string that describes my map, then I convert string to array.
So can you tell me (if possible), how to convert this array1 (or string) to array2?
Lets say I have only 25 tiles (map from level editor is array1)
array1 =
1,1,1,1,1,
2,2,2,2,2,
3,3,3,3,3,
4,4,4,4,4,
5,5,5,5,5
I need this:
array2 =
[
[1,1,1,1,1],
[2,2,2,2,2],
[3,3,3,3,3],
[4,4,4,4,4],
[5,5,5,5,5]
];
UPDATE:
So I need 2d array to build level container.
I do not have experience with tile based games, but here you can see what I do.
Let's say I have 2d array and this is how I create a new level container:
for (i = 0; i < array2.length; i++)
{
for (var j = 0; j < array2[i].length; j++)
{
tile = new Tile();
tile.name = "" + i + j;
tile.x = j * tile.width;
tile.y = i * tile.height;
levelContainer.addChild(tile);
tile.gotoAndStop(array2[i][j]+1);
tile.addEventListener(MouseEvent.MOUSE_DOWN,
buildingLeve);
}
}
addChild(levelContainer);
I have tried to get 2d array from single array as Rudolfwm and Marcela suggested, but when I want to edit a level container using new array2, my tiles go on wrong frames.
For example, if correct frame is 1, tile goes to frame 11,
This code above (building level) works if I create my own 2d array, but not if I convert string to 2d array as suggested.
Try array1[x+y*row] which gives the same result as copying all your data to array2[x][y].
Or if you insist on 2d arrays:
var array2 = new Array(row);
for (var y = 0; y < row; y++) {
array2 [y] = new Array(column);
for(var x=0; x < column; x++) {
array2 [y][x]=array1[x+y*row];
}
}
You start with a String and convert that into an Array using String.split().
Once you have a temporary array, you use a nested loop to populate the final array (arr21).
var row:int = 5;
var column:int = 5;
var arr1:String = "1,1,1,1,1,2,2,2,2,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5";
var tempArr:Array = arr1.split(',');
var arr2:Array = [];
for(var i:int = 0; i < row; i++)
{
arr2[i] = []; // create a new row array
for(var j:int = 0; j < column; j++)
{
// grab the first item from the temp array and push it onto the row array
arr2[i].push(tempArr.shift());
}
}
NOTE: This is not optimized, and could become quite laggy with larger level maps. This is just to give you an idea of where you can start.

Is Splice faster than Shift + Pop? Why?

I've heard from some nonspecific sources that using a combination of shift() and pop() is faster than using splice(). I haven't run a benchmark test yet and will most likely not for a while since I'm out of town, but is this true, and if so, why?
If you don't need to retain the order of your Array, using .pop() is much, much faster than .splice().
.splice() will remove an element at a given index. What happens then is that every other element in the array after that one will need to have its position in the array reduced by 1. This can be slow if your array is large and the index at which you remove an element is small.
Using .pop(), you can remove this process of re-indexing everything entirely. Instead of removing an element at a given index, you can use .pop() to remove the last item from the array. From here, all you need to do is replace the item you want to remove with the one you got from using .pop(). As mentioned, this will of course mean that the order of your elements is not maintained.
Code examples:
.splice():
var list:Array = [0,1,2,3,4,5];
list.splice(2, 1);
trace(list); // 0,1,3,4,5
And then .pop():
var last:* = list.pop();
list[2] = last;
trace(list); // 0,1,5,4 - Notice we move 5 to the index 2 and lose the order.
And here we have the all-important actual performance tests:
var list:Array = [];
function fillList():void
{
for(var i = 0; i < 200000; i++) list.push(i);
}
function emptyViaSplice():void
{
fillList();
var t:Number = getTimer();
while(list.length > 0)
{
list.splice(0, 1);
}
trace("emptyViaSplice: " + (getTimer() - t));
}
function emptyViaPop():void
{
fillList();
var t:Number = getTimer();
while(list.length > 0)
{
if(list.length == 1) list.pop();
else
{
var l:* = list.pop();
list[0] = l;
}
}
trace("emptyViaPop: " + (getTimer() - t));
}
The results:
emptyViaSplice(); // 12153 ms
emptyViaPop(); // 37 ms
I tried another code and the result shows below:
var length = 200000;
function fillList() {
var list = [];
for(var i = 0; i < length; i++)
list.push(i);
return list;
}
function halfViaSplice() {
var list = fillList();
var startTime = new Date();
list.splice(length/2, length/2);
var endTime = new Date();
console.log("halfViaSplice: " + (endTime - startTime));
}
function halfViaPop() {
var list = fillList();
var startTime = new Date();
while(list.length > length/2) {
list.pop();
}
var endTime = new Date();
console.log("halfViaPop: " + (endTime - startTime));
}
halfViaSplice();
halfViaPop();
The results:
halfViaSplice: 0
halfViaPop: 4
I agree with Marty's answer, but if you remove all the last data from one index but not just remove in a hole position, I would advice splice than pop.
Classic algorithms sometimes are faster. Making no changes on the Array size is another way to achieve a very fast performance.
After several tests made with performance.now(), I found out the in the case below, this classic algorithm is faster than any method that changes array sizes and, like all javascript codes, gets faster with local vars.
var ArrayLength = Array.length; // always prefer the simplest references.
for (i=1; i < ArrayLength; i++) {Array[(i-1)] = Array[i];}
Array [ArrayLength-1] = [[newdata1],[newdata2]];
Compared in equal circumstances, the code above, has proven to be slightly faster than
Array.shift(); Array.push([[newdata1],[ newdata2]]);
For small arrays (20 to 60 indexes) that are cyclically changed, you should definitely consider classic algorithms.

Trouble creating a spectrogram

I know it was asked a thousand times before, but I still can't find a solution.
Searching SO, I indeed found the algorithm for it, but lacking the mathematical knowledge required to truly understand it, I am helplessly lost!
To start with the beginning, my goal is to compute an entire spectrogram and save it to an image in order to use it for a visualizer.
I tried using Sound.computeSpectrum, but this requires to play the sound and wait for it to end, I want to compute the spectrogram in a way shorter time than that will require to listen all the song. And I have 2 hours long mp3s.
What I am doing now is to read the bytes from a Sound object, the separate into two Vectors(.); Then using a timer, at each 100 ms I call a function (step1) where I have the implementation of the algorithm, as follows:
for each vector (each for a channel) I apply the hann function to the elements;
for each vector I nullify the imaginary part (I have a secondary vector for that)
for each vector I apply FFT
for each vector I find the magnitude for the first N / 2 elements
for each vector I convert squared magnitude to dB scale
end.
But I get only negative values, and only 30 percent of the results might be useful (in the way that the rest are identical)
I will post the code for only one channel to get rid off the "for each vector" part.
private var N:Number = 512;
private function step1() : void
{
var xReLeft:Vector.<Number> = new Vector.<Number>(N);
var xImLeft:Vector.<Number> = new Vector.<Number>(N);
var leftA:Vector.<Number> = new Vector.<Number>(N);
// getting sample range
leftA = this.channels.left.slice(step * N, step * (N) + (N));
if (leftA.length < N)
{
stepper.removeEventListener(TimerEvent.TIMER, getFreq100ms);
return;
}
else if (leftA.length == 0)
{
stepper.removeEventListener(TimerEvent.TIMER, getFreq100ms);
return;
}
var i:int;
// hann window function init
m_win = new Vector.<Number>(N);
for ( var i:int = 0; i < N; i++ )
m_win[i] = (4.0 / N) * 0.5 * (1 - Math.cos(2 * Math.PI * i / N));
// applying hann window function
for ( i = 0; i < N; i++ )
{
xReLeft[i] = m_win[i]*leftA[i];
//xReRight[i] = m_win[i]*rightA[i];
}
// nullify the imaginary part
for ( i = 0; i < N; i++ )
{
xImLeft[i] = 0.0;
//xImRight[i] = 0.0;
}
var magnitutel:Vector.<Number> = new Vector.<Number>(N);
fftl.run( xReLeft, xImLeft );
current = xReLeft;
currf = xImLeft;
for ( i = 0; i < N / 2; i++ )
{
var re:Number = xReLeft[i];
var im:Number = xImLeft[i];
magnitutel[i] = Math.sqrt(re * re + im * im);
}
const SCALE:Number = 20 / Math.LN10;
var l:uint = this.total.length;
for ( i = 0; i < N / 2; i++ )
{
magnitutel[i] = SCALE * Math.log( magnitutel[i] + Number.MIN_VALUE );
}
var bufferl:Vector.<Number> = new Vector.<Number>();
for (i = 0; i < N / 2 ; i++)
{
bufferl[i] = magnitutel[i];
}
var complete:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
complete[0] = bufferl;
this.total[step] = complete;
this.step++;
}
This function is executed in the event dispatched by the timer (stepper).
Obviously I do something wrong, as I said I have only negative values and further more values range between 1 and 7000 (at least).
I want to thank you in advance for any help.
With respect,
Paul
Negative dB values are OK. Just add a constant (representing your volume control) until the number of points you want to color become positive. The remaining values that stay negative are usually just displayed or colored as black in a spectrogram. No matter how negative (as they might just be the FFT's numerical noise, which can be a huge negative dB number or even NaN or -Inf for log(0)).

How to remove/reload movieclip/graphic in Flash CS5 with AS3

I have a setupBoard(); and a setupBlocks(); in my function:
function init(e)
{
setupBoard();
removeEventListener(Event.ENTER_FRAME , init);
setupCat();
setupBlocks();
}
function setupBoard()
{
var columns:Array = new Array();
var i,j:int;
var _place:place;
for (i = 0; i < 11; i++)
{
columns = [];
for (j = 0; j < 11; j++)
{
_place = new place();
_place.thisX=i;
_place.thisY=j;
_place.thisDistance=Math.min(i+1,j+1,11-i,11-j)*11;
_place.y = 56 * i + 3;
_place.x = 5 + 71 * j + 35*(i%2);
_place.buttonMode=true;
_place.addEventListener(MouseEvent.CLICK, setBlock);
columns[j] = _place;
// SÆTTER TAL PÅ BRIKKERNE
_place.thisText.text = _place.thisDistance + " - " + _place.thisX + " : " + _place.thisY;
addChild(_place);
}
rows[i] = columns;
}
}
The "place" is the MovieClip
this function loads when the game launches and when the game is finish/completed..
the setupBoard, setup the board ofc, and the setupBlocks setup some movieclips, which contain some graphic.
Here's my question, how do I remove/reload all the blocks when the game enters that function again?
At the moment they are just placed upon each other, which I don't like at all.
If I understood correctly, what you want to do is remove all the previous blocks (from the last time you ran the setup function) when you run setup a second time.
To do that, you should create a function which loops your rows and columns Arrays, and for each Place object it find, it does the following: removes it from the stage, removes all Event Listeners, and finally sets it to null. Your function could look something like this (and you would call it just before calling setup again):
for (i = 0; i < rows.length; i++)
{
var column:Array = rows[i];
for (j = 0; j < column.length; j++)
{
var place:Place = column[j];
if (contains(place))
{
removeChild(place);
}
place.removeEventListener(MouseEvent.CLICK, setBlock);
place = null;
}
column = [];
}
row = [];
I just wrote that straight into the box, so it's not tested. But basically it does the three things necessary to get those objects removed from the view, and clears up anything that would stop them from being freed from memory by the garbage collector.
Hope that helps.
Debu

How to filter a specific color from a bitmapData object (or byte-array)

I'm looking for an efficient way to filter a specific color from a bitmapData object in ActionScript 3. Currently I use a loop with readByte32(). This takes about a second to process which is unacceptable. I have been trying to get paletteMap() to work but so far haven't been able to grasp its API (any truly useful links? Google has failed me...).
Here's my current logic, which I want to improve:
var n:int = bitmapData.width;
for (var i:int = 0; i < n; i++) {
var m:int = bitmapData.height;
for (var j:int = 0; j < m; j++) {
var color:int = bitmapData.getPixel(i, j);
if (color == 0xCACACA) {
bitmapData.setPixel32(i, j, 0x00000000);
}
}
}
I can get slightly better performance from using Vectors but it's only marginally better...
var v:Vector.<uint> = bitmapData.getVector(bitmapData.rect);
var n:int = bitmapData.width * bitmapData.height;
for (var i:int = 0; i < n; i++) {
var color:uint = v[i];
v[i] = color == 0xFFCACACA ? 0x00000000 : color;
}
bitmapData.setVector(bitmapData.rect, v);
I really think there must be a better way to do this that only takes a few 100 milliseconds. If anyone can unlock the mysteries of bitmapData for me, you will be the new leader of my people.
PS I am using bitmapData.lock() and unlock(); I just didn't post the boilerplate stuff.
An easy way is using the threshold method. It's a bit cumbersome at first, but it's pretty fast (as fast as you'll get, I think)
This will change every red pixel (considering red only a pixel whose value is exactly 0xffff0000) to blue (0xff0000ff).
var colorToReplace:uint = 0xffff0000;
var newColor:uint = 0xff0000ff;
var maskToUse:uint = 0xffffffff;
var rect:Rectangle = new Rectangle(0,0,bitmapData.width,bitmapData.height);
var p:Point = new Point(0,0);
bitmapData.threshold(bitmapData, rect, p, "==", colorToReplace,
newColor, maskToUse, true);
Flash has an API to a shader like language called pixel bender that may be of use to you in this case. Here is a tutorial from adobe on how to apply a pixel bender filter to an image in flash.
Otherwise you could processes rows at a time. (Note a slight error in your code was to re-get the height on each iteration of width):
private var currentRow:Number = 0;
private var timer:Timer;
public function processImage(event:Event=null):void
{
var m:int = bitmapData.height;
for (var j:int = 0; j < m; j++)
{
if (bitmapData.getPixel(currentRow, j) == 0xCACACA)
{
bitmapData.setPixel32(currentRow, j, 0x00000000);
}
}
currentRow++;
if(currentRow < bitmapData.width)
{
timer = new Timer(1, 500);
timer.addEventListener(TimerEvent.COMPLETE, processImage);
timer.start();
}
}
The processing will take a bit longer but at least your display won't be blocked.