Generate 'n' unique random number within a range - google-apps-script

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

Related

Find all empty cells in several columns

Is there an easy way to color the rows containing empty cells in specific columns? So far, I only came up with a solution to highlight the cells themselves, but this script also takes some time to run.
Will appreciate any advice or guidance!
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('FRs & NFRs')
var columns = ['J17:J','L17:L', 'V17:V', 'AB17:AB','AI17:AI', 'AK17:AK'];
for(var col in columns){ //loop to iterate the desired ranges
var range = sheet.getRange(columns[col]);
//range.activate();
var values = range.getValues();
var formulas = range.getFormulas();
//for each row that data is present
for(var i = 0; i < values.length; i++) { //loop to iterate the rows
for( var j = 0; j< values[i].length ; j++){ //loop to iterate the columns
var cell = range.getCell(i+1, j+1);
if ( values[i][j] == "" )
{
cell.setBackground('red');
cell.activate
}
else
{ cell.setBackground('white')
}
Use ConditionalFormatRuleBuilder instead. This will create a Conditional format rule in your Sheet.
Try this.
Code:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('FRs & NFRs');
var ranges = sheet.getRangeList(['J17:J','L17:L', 'V17:V', 'AB17:AB','AI17:AI', 'AK17:AK']);
var rule = SpreadsheetApp.newConditionalFormatRule()
.whenCellEmpty()
.setBackground('red')
.setRanges(ranges.getRanges())
.build();
var sheetRules = sheet.getConditionalFormatRules();
sheetRules.push(rule);
sheet.setConditionalFormatRules(sheetRules);
}
Output:
Conditional Formatting:
Highlighting empty cells using rangeList.setBackgroundColor(color) ​
In my code below, I collected the cell name of the empty cells and use it to create a RangeList. Then I used the RangeList to set the background to red. This reduced the Google Sheet Service calls from thousands to 11.
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
function highlightEmptyCell(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('FRs & NFRs')
var rangeList = sheet.getRangeList(['J17:J','L17:L', 'V17:V', 'AB17:AB','AI17:AI', 'AK17:AK']);
var rangeArray = rangeList.getRanges();
var ranges = [];
rangeArray.forEach(range =>{
var data = range.getValues();
for( var i = 0; i < data.length; i++ ){
for( var j = 0; j < data[0].length; j++ ){
if(data[i][j] == ""){
var cellName = columnToLetter(range.getColumn()) + (i + 17)
ranges.push(cellName);
}
}
}
});
sheet.getRangeList(ranges).setBackground("red");
}
Reference:
Class ConditionalFormatRuleBuilder
RangeList.setBackgroundColor(color)

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

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);
}

Google Sheets Script - Repeating Multiple Strings X Times in Multiple Rows

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);
}

google apps script two columns summary

I have a google spreadsheet with two columns corresponding to lessons: the first with names of the porfessors (occasionally repeating themselves) and the second with numbers (number of hours). I would like to have as output two columns, the first with the names of the porfessors and the second with the sum of all the hours
I tried with the following code, but it seems to give me back two arrays with the initial colums, as if the condition if (names[names.length-1] == namesColumn[i]) is never met.
What am I doing wrong?
function resumeProfessors() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var namesColumn = sheet.getRange("C4:C31").getValues();
var lessonsColumn = sheet.getRange("G4:G31").getValues();
var names = [];
names.length = 0;
var lessons = [];
lessons.length = 0;
namesColumn.sort();
for (var i = 0; i < namesColumn.length; i++) {
if (names[names.length-1] == namesColumn[i]){
lessons[lessons.length-1] = lessons[lessons.length-1] + lessonsColumn[i];}
else{
sheet.getRange(i+4, 9).setValue(names[names.length-1] + namesColumn[i]);
names[names.length] = namesColumn[i];
lessons[lessons.length] = lessonsColumn[i];
};}
writeResume(names, lessons);
}
Ty
Given your use-case, I'd recommend using a Pivot table or the =QUERY formula.
However, assuming your input sheet looks something like this -
And the expected output is something like this -
You can try the below code -
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName('Sheet1');
var output = ss.getSheetByName('Sheet2');
var inputValues = input.getDataRange().getValues();
Logger.log(inputValues)
for (var i = 1; i < inputValues.length; i++) {
var name = inputValues[i][0];
var totalHours = [];
for (var j = 0; j < inputValues.length; j++) {
var hours = inputValues[j][1];
if (name == inputValues[j][0]) {
totalHours.push(inputValues[j][1]);
}
}
var outputValues = output.getDataRange().getValues();
var newEntry = true;
for (var k = 0; k < outputValues.length; k++) {
if (name == outputValues[k][0]) {
newEntry = false;
}
}
if (newEntry) {
output.appendRow([name,totalHours.reduce(function(a, b) {return a + b})]);
}
}
}
Hope this helps.

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.