Google Sheets Script - Repeating Multiple Strings X Times in Multiple Rows - google-apps-script

How can I repeat 'SAT' 10 times in a row and then 'SUN' times in a row more efficiently?
I know I could use copy/paste, click/drag, custom functions (like this) and combinations of built-in functions like TRANSPOSE(SPLIT(TRIM(REPT(). Those are great if you don't have to use them often. However, with more than just 'SAT' and 'SUN' as values, that quickly becomes cumbersome.
I asked a similar question a few days ago. Working off the solutions provided by dev1998 and Serge insas, I created a working script below.
function testAtA1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.setActiveSheet(ss.getSheetByName('TEST'));
// start columns and rows
var currentRow = 1;
var startColumn = 1;
var numRows = 1; // Change to match number of items listed for a given variable
var numColumns = 1;
var day1 = [
['SAT']
]
var day2 = [
['SUN']
]
Logger.log(day1);
Logger.log(day2);
var day1Length = day1.length;
var day2Length = day2.length;
Logger.log(day1Length);
Logger.log(day2Length);
var currentRow2 = currentRow + 10
// ranges where values will be placed
for (i = 0; i < 10; i++) {
var target1 = sheet.getRange(currentRow, startColumn, numRows, numColumns).setValues(day1);
currentRow = currentRow + 1; // Change to match number of items listed for a given variable
}
for (i = 0; i < 10; i++) {
var target2 = sheet.getRange(currentRow2, startColumn, numRows, numColumns).setValues(day2);
currentRow2 = currentRow2 + 1
}
}
In addition to what I have above, I also tried
var days = [['SAT'],['SUN']]
I was able to get that to alternate SAT and then SUN X times, but not get all SAT in a row and all SUN in a row. With the first snippet I provided I was able to separate them but, I'm curious to know if there is an easier/better way.

function testAtA1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.setActiveSheet(ss.getSheetByName('Sheet1'));
// start columns and rows
var currentRow = 1;
var startColumn = 1;
var numRows = 10; // Change to match number of items listed for a given variable
var numColumns = 1;
var days = ['SAT','SUN','MON']
for (d = 0;d < days.length ; d++) {
var data = days[d]
var target1 = sheet.getRange(currentRow, startColumn, numRows, numColumns).setValue(data);
currentRow = currentRow+10
}
}
By adding another variable d and making a another loop with d
lesser than days.length , We have a desired solution.

=ARRAYFORMULA(TRIM(TRANSPOSE(SPLIT(QUERY(REPT(D1:D7&"😋",10),,9^99),"😋"))))
D1:D7 will contain SAT through FRI

Assuming you just want to append the rows to the spreadsheet, this will do the trick:
function addText(){
var ss = SpreadsheetApp.getActive().getActiveSheet();
var satRow = [];
for (var i = 0; i < 10; i++){
satRow.push("SAT");
}
var sunRow = [];
for (i = 0; i < 10; i++){
sunRow.push("SUN");
}
ss.appendRow(satRow);
ss.appendRow(sunRow)
}
appendRow() takes a 1-dimensional array as an argument. So just build a 1D array and append it.
If you need to insert the rows at an arbitrary point, you're probably better off going with a custom function:
/**
* Fills a range specified in rows and columns with the specified value
*
* #param {number} Number of rows to fill
* #param {number} Number of columns to fill
* #param {string} input The value to iterate.
* #return An array containing the values
* #customfunction
*/
function fillRange(rows,cols,str){
var output = [[]];
for (var i = 0; i < cols; i++){
output[0].push(str);
}
for (var i = 1; i < rows; i++){
output.push(output[0]);
}
return output;
}
To use it, type in a cell =fillRange(1,2,"SAT") to fill 2 columns in one row with "SAT".

again a more efficient solution using array and bulk read/write as recommended in best practice...
function testAtA1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('TEST');
// start columns and rows
var currentRow = 1;
var startColumn = 1;
var numRows = 1; // Change to match number of items listed for a given variable
var numColumns = 1;
var day1 = 'SAT';
var day2 = 'SUN';
var data = sheet.getRange(1,1,sheet.getMaxRows(),sheet.getMaxColumns()).getValues();
var currentRow2 = currentRow + 10 ;// ranges where values will be placed
for (i = 0; i < 10; i++){
data[i][0] = day1;
data[i+10][0] = day2;
}
sheet.getRange(1,1,data.length,data[0].length).setValues(data);
}

Related

Script splits text and adds 1 to value doesnt record the correct number

For the life of me I dont know what is wrong with this code it records the correct number but doesn't add the number correctly unless i type it in myself.
https://docs.google.com/spreadsheets/d/1kxIzE_HcPWd82LpGqCXNxg0yczhcnA6yOYRnB4GuAZo/edit?usp=sharing
var column = 0;
var HEADER_ROW_COUNT = 1;
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var worksheet = spreadsheet.getSheetByName("RawData");
var rows = worksheet.getDataRange().getNumRows();
var vals = worksheet.getSheetValues(1, 1, rows, 1);
var max = 0;
for (var row = HEADER_ROW_COUNT; row < vals.length; row++) {
var splitstring = vals[row][0].toString().split(" ");
var splitstring = vals[row][0].toString().split(" ");
var id = splitstring[1];
Logger.log(id)
if (max < id) {
Logger.log(id)
max = id+1;
Logger.log(max)
}
}
var LastRow = worksheet.getLastRow();
worksheet.getRange(LastRow+1,1).setValue("PO# " + max.toString());
Although I'm not sure whether I could correctly understand your goal, in your script, var id = splitstring[1]; retrieved by var splitstring = vals[row][0].toString().split(" "); is the string type. I thought that this might be the reason of your issue. When this is reflected to your script, it becomes as follows.
From:
var id = splitstring[1];
Logger.log(id)
if (max < id) {
To:
var id = Number(splitstring[1]);
Logger.log(id)
if (max <= id) {
Note:
In your script, for example, you might be also able to use var max = Math.max(...vals.map(([v]) => Number(v.split(" ").pop()))) + 1; instead of your for loop.
Reference:
split()
Tryu this:
function myfunk() {
var ss = SpreadsheetApp.getActive();
var sh = ss.getSheetByName("RawData");
Logger.log(parseInt(sh.getRange(sh.getLastRow(), 1).getValue().split(' ')[1]) + 1);
}

How can I optimize my code using batch operations?

I want to perform a basic multiplication on a range of values in my spreadsheet, and then divide those values by a range of values from a column, note that my range is 8 columns long and my division range is one column long.
I have this code:
function multiply() {
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("1iXQxyL3URe1X1FgbZ76mEFAxLnxegyDzXOMF6WQ5Yqs"));
var sheet = doc.getSheetByName("json");
var sheet2 = doc.getSheetByName("tabla de frecuencias");
var sheet3 = doc.getSheetByName("Template");
var range = sheet2.getDataRange();
var numRows = range.getNumRows();
var numCols = range.getNumColumns()-1; //This will get the division values which are located in the last column
var targ = sheet2.getLastColumn();
for (var i = 2; i <= numRows; i++) {
for (var j = 2; j <= numCols; j++) {
var A = range.getCell(i,j).getValue();
var value = A;
for (var v = 1; v <= numRows; v++) {
var T = range.getCell(v,targ).getValue();
range.getCell(i,j).setValue(value*100/T);
}
}
}
}
It's very slow, it reads and writes on each cell from a sheet where I have numeric values ready to be multiplied by 100 and divided by a value located in a single column, this value is different for each row.
My script gets the job done extremly slowly, batch operations appear promising, if that's not the best solution, I will accept any other alternate solution regardless of the question title.
I think this is what your looking for.
function multiply()
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName("Sheet60");
var rg=sh.getDataRange();
var dataA=rg.getValues();
for(var i=1;i<dataA.length;i++)
{
for(var j=1;j<dataA[0].length-1;j++)
{
var value=dataA[i][j];
for (var v=1;v<dataA.length;v++)
{
var T=dataA[v][dataA[0].length-1];//the value in the last column on this row
Logger.log('v=%s dataA[%s][%s]=%s',v,i,j,dataA[i][j]);
dataA[i][j]=value * 100 / T;//This seems wrong because it puts a different value into dataA[i][j] which doesn't change inside this inner loop and so only the last value remains in dataA[i][j]
}
}
}
rg.setValues(dataA);
}
Try to minimize the use of getValue and replace with one getValues to get all values in two dimensional array. Then setValues all at one time at the end of the loop.
Okay made another change. Getting more reasonable results.
I'm beginning to think that you may not actually want the v loop at all. Take a look at this one and look at the Logger. We just write the data once.
function percentages()
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName("Sheet60");
var rg=sh.getDataRange();
var dataA=rg.getValues();
for(var i=1;i<dataA.length;i++)
{
for(var j=1;j<dataA[0].length-1;j++)
{
var value=dataA[i][j];
var T=dataA[i][dataA[0].length-1];
dataA[i][j]=value * 100 / T;
Logger.log('dataA[%s][%s]=%s',i,j,dataA[i][j]);
}
}
rg.setValues(dataA);
}
This is the output for this version.

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

how to write a function equivalent to ctrl + r in app script(google spread sheet)

so i am to create a doc letting the users enter the random function themselves and upon clicking a button I need to have those block where the random number is generated, updated and get the average and record the average number in a different range of block.
function run2Button(){
var sheet = SpreadsheetApp.getActiveSheet();
var range1 = sheet.getRange("b4:q7");
var range2 = sheet.getRange("b17:e136");
var range3 = sheet.getRange("b8:q8");
for (var i = 1; i<=120; i ++){
for (var col = 1; col <= range1.getWidth(); col++) {
for (var row = 1; row <= range1.getHeight(); row++) {
var number = Math.floor(Math.random() * 6) + 1;
range1.getCell(row, col).setValue(number);
}
}
var n2 = range3.getCell(1, 1).getValue();
var n9 = range3.getCell(1, 4).getValue();
var n36 = range3.getCell(1, 9).getValue();
var n64 = range3.getCell(1,16).getValue();
range2.getCell(i, 1).setValue(n2);
range2.getCell(i, 2).setValue(n9);
range2.getCell(i, 3).setValue(n36);
range2.getCell(i, 4).setValue(n64);
}
};
formatting somewhat sloppy. Initially I plan to do the random number generator myself but ultimately let them to enter the command: '=randbetween(1,6) '
themselves, but I need to, via app script, recalculate or refresh the cell how can I do that.

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.