Change text to fixed number of digits (add zeroes) - enhance speed [duplicate] - google-apps-script

This question already has answers here:
Adding Leading Zeros t in Google App Script, Spreadsheet
(2 answers)
Closed 1 year ago.
Goodday,
I have been using this function to make sure that all values in column A have either 5 digits (add zero's if less than 5) OR remain blank if there is no value in the cell.
It works fine as long as the number of rows is low.
My number of rows have increased to almost 10.000 so it takes a really really long time to finish.
Can anyone help me speed this up to get and get results as in the green Col1? (all values are text)
function set5Digits () {
var app = SpreadsheetApp.getActive();
var ws = app.getSheetByName("Sheet1");
var values = ws.getDataRange().getValues();
for (var i = 2; i < values.length; i++) {
var n = ws.getRange("a"+ i).getValue();
var format = 0;
var length = n.toString().length;
var result = "";
if(length<5 && length>0){
format = 5;
var z = format - length;
for(var x = 0; x < z; x++){
result = result + "0";
}
ws.getRange("a" + i).setNumberFormat('#').setValue(result+n.toString());
}
}
}

Access to the sheet is slow, therefore, with a large increase in the number of rows, the total running time of the script increases significantly.
Therefore it is better to read all the data into an array once, then process them (this is fast) and then unload the processed array onto a sheet.
Try it:
function set5Digits() {
var app = SpreadsheetApp.getActive();
var ws = app.getSheetByName("Sheet1");
var lr = ws.getLastRow();
var values = ws.getRange(2, 1, lr).getValues();
var len = values.length;
var res = [];
var format = 5;
for (var i = 0; i < len; i++) {
var n = values[i];
var length = n.toString().length;
var result = "";
if (length >= format) {
res.push([n.toString()]);
} else if (length == 0) {
res.push([""]);
} else {
var z = format - length;
for (var x = 0; x < z; x++) {
result = result + "0";
}
res.push([result + n.toString()]);
}
}
ws.getRange(2, 1, len, 1).setNumberFormat('#').setValues(res);
}

function set5Digits() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet1");
const vs = sh.getRange(2, 1, sh.getLastRow() - 1, 1).getValues();
let c = sh.getRange(2, 1, sh.getLastRow() - 1, 1).getValues();
vs.forEach((r, i) => {
c[i][0] = Number(r[0]).toFixed(5).toString();
});
sh.getRange(2, 1, sh.getLastRow() - 1, 1).setNumberFormat('00000X').setHorizontalAlignment('right');
sh.getRange(2, 1, c.length, c[0].length).setValues(c);
}

Related

Google sheets Count cells by color if value in cell is equal to 1 or more

I'm working in Google sheets. I'm not a developer but I got this function from a YouTube video. In my Google sheet I have a list of task in column A and these tasks award 0-5 points when complete. The function I have looks at the range specified counts how many of them are green. works fine but I only interested in counting how many tasks are complete that award points of 1 or more. Here is the function:
function countColoredCells(countRange,colorRef,rangeSum) {
var activeRange = SpreadsheetApp.getActiveRange();
var activeSheet = activeRange.getSheet();
var range = activeSheet.getRange(countRange);
var rangeHeight = range.getHeight();
var rangeWidth = range.getWidth();
var bg = range.getBackgrounds();
var colorCell = activeSheet.getRange(colorRef);
var color = colorCell.getBackground();
var count = 0;
for(var i = 0; i < rangeHeight; i++)
for(var j = 0; j < rangeWidth; j++)
if( bg[i][j] == color )
count = count+1;
return count;
};
To explain this code a bit more it takes 3 augments. The First points to a that has a range in it. In my sheet in cell C174 I have written there C2:C173. So I give the function C174 for the first augment and it looks there for the range. The second augment takes a cell with the color you want to be counted. So I have the G5 cell Colored Green so I give the functions second augment G5 because I want it to count the green cells. The third augment is a little tricky to explain but you give the function a cell that as a Boolean check box thing and when you change it's value it refreshes the function to update it's count. Here is my google sheets https://docs.google.com/spreadsheets/d/1W05-UKB-bBGMqOUC5OO9XirxnRBfJeMOj3ZMHoENcSQ/edit?usp=sharing There is a second custom function I am using here that I got from the YouTube video that just looks at the range of colored and sums their value. Also if you are looking at this sheet and are confused. the values in the colored cells that are formatted to be hidden.
You can try to make these three changes in the function:
function countColoredCells(countRange,colorRef,rangeSum) {
var activeRange = SpreadsheetApp.getActiveRange();
var activeSheet = activeRange.getSheet();
var range = activeSheet.getRange(countRange);
var rangeHeight = range.getHeight();
var rangeWidth = range.getWidth();
var bg = range.getBackgrounds();
var colorCell = activeSheet.getRange(colorRef);
var color = colorCell.getBackground();
var points_range = countRange.replace(/[A-z]+/g,'B'); // <--- HERE
var points = activeSheet.getRange(points_range).getValues(); // <--- HERE
var count = 0;
for(var i = 0; i < rangeHeight; i++)
for(var j = 0; j < rangeWidth; j++)
if( bg[i][j] == color && points[i][0] > 0 ) // <--- HERE
count = count+1;
return count;
};
Count Colors
function colorCounter() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");//source sheet
const cs = sh.getDataRange().getBackgrounds();
const vs = sh.getDataRange().getValues();
let obj = { pA: [] };
//color counter
vs.forEach((r, i) => {
r.forEach((c, j) => {
if (!isNaN(c) && c >= 1) {
if (!obj.hasOwnProperty(cs[i][j])) {
obj[cs[i][j]] = { color: cs[i][j], count: 1, row: i + 1, col: j + 1 }
obj.pA.push(cs[i][j]);
} else {
obj[cs[i][j]].count += 1;
}
}
})
})
let vA = [["Color Counter"]];
let cA = [["#ffffff"]];
obj.pA.forEach(p => {
cA.push([obj[p].color]);
vA.push([obj[p].count])
})
const osh = ss.getSheetByName("Sheet1");//Display Sheet
osh.clear();
osh.getRange(1, 1, vA.length, 1).setValues(vA);
osh.getRange(1, 1, cA.length, 1).setBackgrounds(cA);
}
Demo:

Code takes 2 minutes to paste values on another tab. How to optimize it?

I've done this code on Google Script, but it's taking way too long (it completes 10-15 lines in 30 minutes. I don't know how to optimize it more. Could the size of the spreadsheet be influencing it's processing? If not, how do I change it to improve?
function PreenchePlanilhaFinal() {
var App = SpreadsheetApp;
App.getActiveSpreadsheet().getSheetByName('MacroHelp').getRange(1,1).activate();
var helpMacro = App.getActiveSpreadsheet().getActiveSheet(); //aba que vc está ativo
var lastLine = helpMacro.getLastRow();
for (var i = 214; i < lastLine; i++){
if (helpMacro.getRange(i, 17).getValue() == "")
{
var regionCode = helpMacro.getRange(i, 5).getValue();
var nomeAba = ""; //inicializo a variável da região
for (var j = 1; j < lastLine; j++){
if (regionCode == helpMacro.getRange(j, 20).getValue()){
nomeAba = helpMacro.getRange(j, 21).getValue();
break;
}
}
var email = helpMacro.getRange(i,1).getValue();
var aba = App.getActiveSpreadsheet().getSheetByName(nomeAba);
aba.getRange(1,1).activate(); //ativo a aba
var lastLineNovaAba = aba.getLastRow();
for (var k = 1; k <= lastLineNovaAba; k++){
if (email == aba.getRange(k, 8).getValue()){
App.getActiveSpreadsheet().getActiveSheet().getRange(k, 31, 1, 11).setValues(helpMacro.getRange(i, 6, 1, 11).getValues());
}
}
helpMacro.getRange(i, 17).activate().setValue("Feito");
}
}
}
You are trying to optimise script function PreenchePlanilhaFinal().
Execution time is affected by many getValue statements; each of these (particularly when repeated in a loop) can be quite costly. The solution is, where possible, to 1) getValues() once only and 2) do this outside the loop.
The following script is untested, but it demonstrates the basic methodology.
function PreenchePlanilhaFinal() {
var App = SpreadsheetApp;
App.getActiveSpreadsheet().getSheetByName('MacroHelp').getRange(1,1).activate(); // get MacroHelp A1
var helpMacro = App.getActiveSpreadsheet().getActiveSheet(); //aba que vc está ativo
var lastLine = helpMacro.getLastRow();
// new line to get last column
var lastColumn = helpMacro.getlastColumn();
// new line to declare start line as a variable
var startRow = 214;
// define the range and get values
var helpMacroData = helpMacro.getRange(startRow,1,lastLine-startRow+1,lastColumn).getValues();
// declare aba outside the loop, and define the range and get values
var aba = App.getActiveSpreadsheet().getSheetByName(nomeAba);
var abaLR = aba.getlastRow();
var abaLC = aba.getLastColumn();
var abaData = aba.getRange(1,1,abaLR,abaLC).getValues();
// note i = 0 since the values start on line 214
for (var i = 0; i < lastLine; i++){
if (helpMacroData[i][16] == "") // describe variable as array value
{
var regionCode = helpMacroData[i][4];// describe variable as array value
var nomeAba = ""; //inicializo a variável da região
for (var j = 0; j < lastLine; j++){ // set j to 0 since arrays are zero-based
if (regionCode == helpMacroData[0][19]){// describe variable as array value
nomeAba = helpMacroData[j][20];// describe variable as array value
break;
}
}
var email = helpMacroData[i][0];// describe variable as array value
aba[0][0].activate(); //ativo a aba //// describe variable as array value
for (var k = 0; k < abaLC; k++){ set k to 0 since arrays are zero-based, also make "<" not "<="
if (email == aba[k][7]){ // describe variable as array value
App.getActiveSpreadsheet().getActiveSheet().getRange(k, 31, 1, 11).setValues(helpMacro.getRange(i, 6, 1, 11).getValues());
}
}
helpMacro.getRange(i, 17).activate().setValue("Feito");
}
}
}

Script exceeds maximum execution time

I am using a script to first copy a list of all terminated products from "data" tab of the sheet to the "terminated tab"
The data tab looks like below
The code checks if there is an end date written
if it is - the row is copied and pasted in the "terminated" tab
Once all rows (around 2000) are completed
the code the deletes all rows from the "data" tab that have an end date on it
But the code is not very efficient and data is huge - I get a "maximum execution time exceeded" error
function movingTerms() {
var app = SpreadsheetApp ;
var sheet1 = app.getActiveSpreadsheet().getSheetByName("data") ;
var sheet3 = app.getActiveSpreadsheet().getSheetByName("Terminations");
var range1 = sheet1.getRange(2, 1, sheet1.getLastRow() - 1,9);
var range3 = sheet3.getRange(2, 1, sheet3.getLastRow(), 9);
var values1 = range1.getValues();
var values3 = range3.getValues();
var rowcount = sheet1.getLastRow();
var row_deleted = 0;
for (var i = 0; i < (sheet1.getLastRow() - 1); i++)
{
if (values1[i][4] !== "")
{
var rowtodelete = sheet1.getRange(i + 2, 1, 1, 10);
var rowtoadd = sheet3.getRange(sheet3.getLastRow() + 1, 1);
rowtodelete.copyTo(rowtoadd);
}
}
for (var k = 0; k < values1.length; k++)
{
var row = k + 1 - row_deleted;
if (values1[k][4] !== "")
{
var getridof = row +1;
sheet1.deleteRow(getridof);
row_deleted++;
}
}
}
I generally like to see the spreadsheet to do this correctly but this is the way that I would do it.
function movingTerms() {
var ss=SpreadsheetApp.getActive();
var sheet1=ss.getSheetByName("data") ;
var sheet3=ss.getSheetByName("Terminations");
var range1=sheet1.getRange(2, 1, sheet1.getLastRow()-1,9);
var range3=sheet3.getRange(2, 1, sheet3.getLastRow(),9);//You don't really need this
var values1=range1.getValues();
var values3=range3.getValues();//You don't really need this
var rowcount=sheet1.getLastRow();
var rowsdeleted = 0;
for (var i=0;i<values1.length;i++) {
if (values1[i][4]!="") {//column5
var rowtodelete = sheet1.getRange(i-rowsdeleted+2, 1, 1, 10);
var rowtoadd = sheet3.getRange(sheet3.getLastRow()+1,1);//You could also append to sheet 3 if you wish
rowtodelete.copyTo(rowtoadd);
sheet1.deleteRow(i-rowsdeleted+2);
rowsdeleted++;
}
}
}

Generate 'n' unique random number within a range

I need to generate 5 random number within a specific range from 1 to 100 with out duplicates.
A1 = 1(from)
A2 = 100(to)
A3 = 5 (Required random number)
A4,A5,A6,A7,A8 in cell should generate random number
A simple way could be:
Generate a list of the 100 numbers
Shuffle the list using the Fisher-Yates algorithm
Take the first 5 numbers
There are faster ways, but for only 100 integers it should be fine.
Edit: Try this code:
function shuffleArray(array) { // from http://stackoverflow.com/a/12646864/5710637
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function Randomnumber() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("c2:C5");
var min = sheet.getRange("A1").getValue();
var max = sheet.getRange("A2").getValue();
var numbers = []
for (var i = min; i <= max; i++) {
numbers.push(i);
}
shuffleArray(numbers)
var counter = 0;
for (var x = 1; x <= range.getWidth(); x++) {
for (var y = 1; y <= range.getHeight(); y++) {
range.getCell(y, x).setValue(numbers[counter]);
counter++;
}
}
};
As fafl pointed you can use a list.
Generate list according to Range
Pop n Numbers out of them randomly one by one
Write popped Numbers to sheet
Here is an example.
/*Note: The Code does not have validations like the random number needed should be less
than the range etc. You should take care of such issues and improvise the code for the
same.
Rest of the code is optimized and makes a single read and write from Spread Making it
run fast*/
function myFunction() {
//var ss = SpreadsheetApp.getActiveSpreadsheet();
var ss = SpreadsheetApp.openById("1_xoBxknhDm1pM3MBw0Jbat3BTV4HXep7nZlOPw4tEWg");
var sheet = ss.getSheets()[0];
var values = sheet.getRange(1, 2, 3, 1).getValues();
var nRandomNumbers = getNRandomNumbers(values[0][0], values[1][0], values[2][0]);
sheet.getRange(4,2,values[2][0],1).setValues(nRandomNumbers);
}
function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function getNRandomNumbers(from, to, n){
var listNumbers = [];
var nRandomNumbers = [];
for(var i = from; i <= to; i++) {
listNumbers.push(i);
}
for(var i = 0; i < n; i++) {
var index = getRandomNumber(0, listNumbers.length);
nRandomNumbers.push([listNumbers[parseInt(index)]]);
listNumbers.splice(index, 1);
}
return nRandomNumbers;
}
Demo Link:(Please Copy the code into your drive/sheet, can't get the permission working)
Script: https://script.google.com/d/1hsWiGCFZ3DlxiSB3ysTr5ThWvDzThS-vBVzHrJCIEW8zM4_DzndCwGkQ/edit?usp=sharing
Sheet: https://docs.google.com/spreadsheets/d/1_xoBxknhDm1pM3MBw0Jbat3BTV4HXep7nZlOPw4tEWg/edit#gid=0

Apps Script: Construct range of rows from array of row numbers

I have a list of row numbers in a spreadsheet which I need to change the background colour of. As the spreadsheet is quite large (10+ sheets, each with almost 5000 rows), I am trying to construct a range so I can batch set the background, as doing each row individually was taking over the max time of 6 minutes.
Here's the code I have:
// highlight required rows
var first = -1, last = -1;
for(var j = 0; j < rowNumsToHighlight.length; j++) {
if(first == -1) {
first = rowNumsToHighlight[j];
continue;
}
// if the current row number is one more than the previous, update last to be the current row number
if(rowNumsToHighlight[j] - 1 == rowNumsToHighlight[j - 1]) {
last = rowNumsToHighlight[j];
continue;
}
// otherwise the last row should be the previous one
else {
last = rowNumsToHighlight[j - 1];
}
var numRows = (last - first) + 1;
var range = sheet.getRange(first, 1, numRows, 4);
if(range.getBackground().toUpperCase() != highlightColour.toUpperCase()) {
range.setBackground(highlightColour);
}
first = -1;
last = -1;
}
rowNumsToHighlight is just an array that looks like: [205,270,271,272,278,279]. So, with that as an example, setBackground should be ran on row 205, on rows 270-272, and on 278-279.
I'm fairly sure the solution is simple, but just can't see it. Thanks for any help.
==== Updated Code ====
Based on Serge's code below, I made it more efficient again by reducing the number of getRange() calls made. Time is down from 78 to 54 seconds.
function updateColours(sheet, array, colour){
var columns = sheet.getLastColumn();
var rows = sheet.getLastRow();
var range = sheet.getRange(1, 1, rows, columns);
Logger.log("Resetting highlight on all rows...");
range.setBackground(null);
var backgrounds = range.getBackgrounds();
for(var n = 0; n < backgrounds.length; n++){
var rowIdx = n + 1;
if(array.indexOf(rowIdx) > -1){
for(var c = 0; c < columns; c++){
backgrounds[n][c] = colour;
}
}
}
Logger.log("Highlighting non-translated rows...");
range.setBackgrounds(backgrounds);
}
Maybe this one is faster(?) and built in a way that will make your work easier (function with arguments).
It writes only once to the sheet (or 2 if you clear colors before writing)...
use like below :
function testBG(){
updateColors(0,[7,8,9,18,19,23]);
}
function updateColors(sheetNum,array){
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheets()[sheetNum];
var columns = sh.getMaxColumns();
var range = sh.getRange(1,1,sh.getMaxRows(),columns);
sh.getRange(1,1,sh.getMaxRows(),columns).setBackground(null);// use this if you want to clear all colors before setting them
var backGrounds = range.getBackgrounds();// get all cells BG
for(var n=0;n<backGrounds.length;n++){
var rowIdx = n+1;
if(array.indexOf(rowIdx)>-1){
for(c=0;c<columns;c++){
backGrounds[n][c]="#F00";// if row number is in the array fill in red
}
}
}
sh.getRange(1,1,sh.getMaxRows(),columns).setBackgrounds(backGrounds);//update sheet in one call
}
test sheet in view only, make a copy to test.
This is how I would do it:
function createRanges() {
var rowNumsToHighlight = [5,7,8,9,18,19];
var arrayLength = rowNumsToHighlight.length;
var loopCounter = 0, thisNumberInArray=0, nextNumberInArray=0, crrentNmbrPlusOne=0;
var currentRangeBegin=0, numberOfRowsInRange=1;
currentRangeBegin = rowNumsToHighlight[0];
for(loopCounter=0; loopCounter < arrayLength; loopCounter+=1) {
thisNumberInArray = rowNumsToHighlight[loopCounter];
nextNumberInArray = rowNumsToHighlight[loopCounter+1];
crrentNmbrPlusOne = thisNumberInArray+1;
if (nextNumberInArray===undefined) {
workOnTheRange(currentRangeBegin, numberOfRowsInRange);
return;
};
if (nextNumberInArray!==crrentNmbrPlusOne) {
workOnTheRange(currentRangeBegin, numberOfRowsInRange);
numberOfRowsInRange = 1; //Reset to 1
currentRangeBegin = nextNumberInArray;
} else {
numberOfRowsInRange+=1;
};
};
};
function workOnTheRange(first,numRows) {
var range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet11').getRange(first, 1, numRows, 4);
range.setBackground("red");
};
I've tested the code and it works.