Random Selection in a Column - javascript

I've modified a code to select a data randomly from A Column cells. The problem I'm facing is, I'm only able to get the data from A1. and rest of the data are not showing. Where did I go wrong?
function getData () {
var SS = SpreadsheetApp.getActiveSheet()
var Avals = SS.getRange("A1:A").getValues();
var numberOfValues = Avals.filter(String).length;
var data = SS.getRange(1,1,numberOfValues).getValues();
for(var i = 0; i < data.length; i++)
{
var j = Math.floor(Math.random()*(data[i].length)); //method of randomization
var element = data[i][j];
return element;
logger.log(element);
}
}

Explanation:
getValues() returns a 2D array. In your case you have a single column, therefore data has the format of: [[a1],[a2],[a3],..]]. data[i] will give you a single element of this array. For example data[0] is [a1] and as you can understand [a1].length is equal to 1 regardles of the chosen index. In other words, data[i].length is 1 for every i and therefore you are always getting the first element.
It is also worth mentioning that you have a return statement inside the for loop and therefore i gets only the first value 0 since the function is terminated when reaches the return statement. You don't need a for loop if you want to get a single random element from the column.
Since data[i].length is equal to 1 this expression Math.random()*(data[i].length) returns a number between 0 and 1 but less than 1. Therefore Math.floor(Math.random()*(data[i].length)) always returns 0 and as a result data[0][0] is equal to the value of the first cell A1.
Please notice that logger.log(element) is wrong. It should be Logger.log(element); but you are not getting any error since return is before that line and the function never reaches that line.
Finally, I used flat() to convert the 2D array to 1D and therefore data.length returns the correct length of the array but also you can now index it with one variable.
Solution:
function getData () {
const SS = SpreadsheetApp.getActive();
const sh = SS.getActiveSheet();
const Avals = sh.getRange("A1:A").getValues();
const numberOfValues = Avals.filter(String).length;
const data = sh.getRange(1,1,numberOfValues).getValues().flat();
const j = Math.floor(Math.random()*(data.length)); //method of randomization
const element = data[j];
Logger.log(element);
return element;
}

Related

Iterate over a large 2D array with nested loop in JavaScript

I am trying to iterate over a large 2D array which is over 3000 rows and 54 columns. Each index contains either a string or integer value. When I try to use a nested for loop I am unable to test it since it just takes to long to complete. How can I get around this issue?
I have this code from an earlier post that does something similar:
function getOrder() {
const srcSheetName = "result";
const dstSheetName = "Order Changes";
const ss = SpreadsheetApp.getActiveSpreadsheet();
// 1. Retrieve source values.
const srcSheet = ss.getSheetByName(srcSheetName);
const [[,,,,,,,, ...header], ...srcValues] = srcSheet.getRange("F1:BQ" + srcSheet.getLastRow()).getValues();
// 2. Create an object using the source values.
const srcObj = srcValues.reduce((o, [a,,,,,,,, ...v]) => {
const temp = v.reduce((s, r, i) => {
if (r.toString() != "") s += `${header[i]} (${r}) `;
return s;
}, "");
return Object.assign(o, {[a]: temp || ""});
}, {});
// 3. Retrieve the header column of destination values.
const dstSheet = ss.getSheetByName(dstSheetName);
const dstRange = dstSheet.getRange(3, 1, dstSheet.getLastRow() - 1);
const dstValues = dstRange.getValues();
// 4. Create the output values using the header column and the object.
const putValues = dstValues.map(([a]) => [srcObj[a] || ""]);
console.log(srcObj)
// 5. Put the values.
dstRange.offset(0, 2).setValues(putValues);
}
The above code matches names on two different sheets and returns the values and headers of each column if a match is found and does so very quickly with the same number of entries. I assume it is so quick because of .reduce to remove unnecessary information in the 2d array.
How can I achieve a similar speed? I would like to search the 2d array for non-zero entries of a row in the last 12 columns. If any of the columns contain a value I would like to return index 0 of that row and continue until the array has been completely cycled through.
I have tried to adjust the above code but I don't understand it entirely and can't manipulate it how I'd like. Here is what I have written:
function getCustomer(){
const srcSheetName = "result";
const dstSheetName = "Allergy";
const ss = SpreadsheetApp.getActiveSpreadsheet();
const srcSheet = ss.getSheetByName(srcSheetName);
const allergyRange = srcSheet.getRange('F1:BQ' + srcSheet.getLastRow()).getValues();
const dstSheet = ss.getSheetByName(dstSheetName);
Logger.log(allergyRange);
for (let i = 0; i < allergyRange.length; i++){
for (let k = 0; k < allergyRange[i].length; k++){
Logger.log("hi");
}
}
}
I would like to nest an if statement in the code I've written but am unaware of a way to refer to a range of indices in a row of a 2d Array and at this point I think this would take too much time to be practical. Any tips would be greatly appreciated.
Assuming allergyRange is formatted like [row, ...] where each row is an array with each of its indices referring to a column:
allergyRange.map(row => row.slice(row.length-12)).forEach((row, i) => {
if(!row.every(val => val == 0)) console.log(allergyRange[i][0])
});
This maps a new array of arrays that contain just the last 12 columns, checks if every value in that row is equal to 0, and if not logs the 0th index of that row.

Automate Hyperlink Creations

I'm trying to automate hyperlink creations on my GSheet.
Here's my script:
function ticketURLGenerator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data");
var range = sheet.getRange("C2:C");
var ticketID = range.getValue();
Logger.log(ticketID);
for(i = 0; i < ticketID.length; i++){
if(ticketID.length === 0){
ticketID.setValue('')
} else if(ticketID.length > 4){
ticketID.setValue('=HYPERLINK' + '("https://mylink.com/'+ticketID+'";'+ticketID+')');
}
}
}
It does nothing but when I change ticketID.setValue by sheet.getRange("C2:C").setValue it put the whole range in the url. We can see with Logger.log(ticketID) that the whole range is selected.
So according to this result, i'm missing how to get the value of each cell individualy in the range and then check if they are long enought to create an individual url. Do I need to use something like range[i] somewhere? I'm lost.
I believe your goal as follows.
You want to retrieve the values from the cells "C2:C".
When the length of value is more than 4, you want to create a formula of HYPERLINK.
When the length of value is less than 4, you don't want to put the formula.
You want to put the formulas to the cells "C2:C".
Modification points:
When range of var range = sheet.getRange("C2:C") is used, the value of var ticketID = range.getValue() is the value of cell "C2". When you want to retrieve values from the cells "C2:C", please use getValues instead of getValue.
In this case, the retrieved value is 2 dimensional array.
When range.getValue() is the string value, ticketID of var ticketID = range.getValue() is also the string. So I think that when ticketID.setValue('##') is run, an error occurs.
In your script, setValue is used in a loop. In this case, the process cost will become high.
And, when sheet.getRange("C2:C" + sheet.getLastRow()) is used instead of sheet.getRange("C2:C"), the process cost will become low a little.
When above points are reflected to your script, it becomes as follows.
Modified script:
function ticketURLGenerator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data");
var range = sheet.getRange("C2:C" + sheet.getLastRow());
var ticketIDs = range.getValues();
var values = ticketIDs.map(([c]) => [c.toString().length > 4 ? `=HYPERLINK("https://mylink.com/${c}";"${c}")` : c]);
range.setValues(values);
}
In this modification, the values are retrieved from the cells of "C2:C" + sheet.getLastRow(), and an array including the formulas and values is created, and then, the array is put to the cells.
And I used the template literal for creating the formula.
Note:
In this case, please use this script with enabling V8 runtime.
References:
getLastRow()
getValues()
map()
Template literals
You just need to apply the HYPERLINK operation to the tickets that their length is more than 4. To achieve that, you can use map() to iterate over all the elements in your list.
Solution:
function ticketURLGenerator() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Data");
const ticketR = sheet.getRange("C2:C"+sheet.getLastRow());
const ticketIDs = ticketR.getDisplayValues().flat();
const hLinks = ticketIDs.map(ti=>{
if(ti.length>4) {
return [`=HYPERLINK("https://mylink.com/${ti}"; ${ti})`]}
else {return [ti]}
})
ticketR.setValues(hLinks);
}

Compare 4 different columns in 2 different Google Sheets

I am trying to compare the data from 2 google sheets. Each sheet has a column that is the identifier (sheet1:H and sheet2:C), if these match then I want to change sheet1:I to the value in sheet2:E. I'm running this code, but get no errors. It's not working though.
I tried to see similar posts this issue but they all seem to be lacking the compare a different column method I am using.
function changestatus() {
// gets spreadsheet A and the range of data
ssA = SpreadsheetApp.openById('IDHERE');
sheetA = ssA.getSheetByName('Sheet1');
dataA = sheetA.getRange('H2:H').getValues();
dataD = sheetA.getRange('I2:I').getValues();
// gets spreadsheet B and the range of data
ssB = SpreadsheetApp.openById('IDHERE');
sheetB = ssB.getSheetByName('responses');
dataB = sheetB.getRange('C2:C').getValues();
dataC = sheetB.getRange('E2:E').getValues();
for (var i = 0; i > sheetA.getLastRow(); i++) {
if (dataA[1][i] == dataB[1][i] && dataC[1][i] != dataD[1][i]){
var value = sheetA.getRange(i+1, 2).getValue(dataD);
sheetB.getRange(i+1, 2).setValue(value);
} // end if
} // end i
Starting results of sheets files would be something like:
Sheet 1
H:(ID) 1 I:(grade) pass
Sheet 2
C:(ID) 1 E:(grade) fail
After Function:
Sheet 1
H:(ID) 1 I:(grade) fail
#tehhowch is quite right; you need to review JavaScript comparison operators, for loop syntax, the format of object returned by Range#getValues, and how to access JavaScript array indices. Each of these contributes to your code problems, but it's reasonable that that we help you along the road a little more.
Loop syntax
This is an easy one. Instead of "i > sheetA.getLastRow()", it should read i < sheetA.getLastRow(). i starts with a value of zero, and its value increases by one at the end of each loop; so you want the loop to process all the values of i that are less than the value of the last row.
Array values
getValues returns a two-dimensional array but the IF statement fails because the array values are back to front.
For example, instead of "dataA[1][i]", it should be dataA[i][0]. There are two changes here:
1 - "i" moves to the first half of the array value (the 'row' value); and
2 - the second half of the array value is [0] (not "[1]"). This is because each variable is only one column wide. For example, dataA only returns the value of column H; same is true for dataB, dataC and dataD - they all return the value of just one column.
Troubleshooting
How could you tell whether the IF statement was a problem? It "looks" OK. One way is to display (or log) the values being returned.
I use Logger.log() (there are other options) to display information in the script editor under "View, Logs". Each time the script is run, the "Logger" statements are updated and you can check their value.
For example, you could insert this code at line 13 (before the loop) to check some values of the data variables.
Logger.log("dataA[1][0] = "+dataA[1][0]);
That line will show: "dataA[1][0] = 2". That's a valid result but you might notice that it is reporting ID=2 but, say, you were expecting a result of ID=1.
So change the line to:
Logger.log("dataA[1][1] = "+dataA[1][1]);
This line shows "dataA[1][1] = undefined". OK, something definitely wrong.
So, let's try:
Logger.log("dataA[0][0] = "+dataA[0][0]);
This line shows "dataA[0][0] = 1". Now that's more like it.
You can make Logger long or short; for example, you might want to evaluate the results of of the variables in one line. So the Logger might look like this:
Logger.log("dataA[0][0] = "+dataA[0][0]+", dataB[0][0] = "+dataB[0][0]+", dataC[0][0] = "+dataC[0][0]+", dataD[0][0] = "+dataD[0][0]);
And it would return:
"dataA[0][0] = 1, dataB[0][0] = 1, dataC[0][0] = Fail, dataD[0][0] = Pass".
This might confirm that you are on the right track, or that you need to debug further
The Failing IF statement
Original line = "(dataA[1][i] == dataB[1][i] && dataC[1][i] != dataD[1][i])"
Corrected line = (dataA[i][0] == dataB[i][0] && dataC[i][0] != dataD[i][0])
Updating the results on Sheet 1
The code here is:
var value = sheetA.getRange(i+1, 2).getValue(dataD);
sheetB.getRange(i+1, 2).setValue(value);
This is confusing and complicates a couple of things.
1 - the value just needs to be "the value in sheet2:E - this was in the IF statement: dataC[i][0]. So value = dataC[i][0]
2 - The goal is "change sheet1:I to the value in sheet2:E". You've already got the value, so focus now on sheet1:I.
Some times it is more simple to define the range and then, on a second line, update the value for that range.
the target sheet is sheetA;
the target row is: i+1 (that was correct);
the target column is: I (or column 9).
So, var range = sheetA.getRange(i+2, 9);
You could check this with "Logger":
Logger.log("range = "+range.getA1Notation()); might return "range = I2".
Then update the value:
range.setValue(value);
Meaningful variable names
It helps (a LOT) to use meaningful variable names. For example, the original code uses:
"dataA" = Sheet1, Column H (contains ID); so maybe this could be "data1_H" or even "targetID.
"dataD" = Sheet1, Column I (contains grade); so maybe this could be "data1_I" or targetGrade.
"dataB" = Sheet2, Column C (contains ID), so maybe this could be "data2_C" or sourceID.
"dataC" = Sheet2, Column E (contains grade); so maybe this could be "data2_E" or sourceGrade.
Summary of changes
function so_changestatus() {
// gets spreadsheet A and the range of data
ssA = SpreadsheetApp.openById('IDHERE');
sheetA = ssA.getSheetByName('Sheet1');
dataA = sheetA.getRange('H2:H').getValues();
dataD = sheetA.getRange('I2:I').getValues();
// gets spreadsheet B and the range of data
ssB = SpreadsheetApp.openById('IDHERE');
sheetB = ssB.getSheetByName('responses');
dataB = sheetB.getRange('C2:C').getValues();
dataC = sheetB.getRange('E2:E').getValues();
for (var i = 0; i < sheetA.getLastRow(); i++) {
if (dataA[i][0] == dataB[i][0] && dataC[i][0] != dataD[i][0]){
var value = dataC[i][0];
var range = sheetA.getRange(i+2, 9);
range.setValue(value);
} // end if
}
}
UPDATE - 1 April 2019
ID on SheetA vs SheetB does NOT match row-by-row
The original code was written on the basis that the ID matched on a row-by-row basis. This is not the case. So a variation in the code is needed to test whether the ID on SheetA exists on SheetB, and then test the respective status.
The evaluation of the sheetA ID on sheetB is done with [indexof] Docs reference.
In this code, I also took the opportunity to make the variable names of the data ranges more meaningful.
Note also: the loop continues while i is less than the lastrow minus one "i < (lastrow-1);". This is necessary because the first row are headers and the data range starts on row 2, so the number of data rows will be the "lastrow minus one" (to a allow for the header row).
function ejb2so_changestatus() {
// gets spreadsheet A and the range of data
// ssA = SpreadsheetApp.openById('IDHERE');
ssA = SpreadsheetApp.getActive();
sheetA = ssA.getSheetByName('Sheet1');
dataA_ID = sheetA.getRange('H2:H').getValues();
data_Status = sheetA.getRange('I2:I').getValues();
//Logger.log("DEBUG: H3 = "+dataA_ID[4][0]+", I3 = "+data_Status[4][0]);//DEBUG
// gets spreadsheet B and the range of data
//ssB = SpreadsheetApp.openById('IDHERE');
ssB = SpreadsheetApp.getActive();
sheetB = ssB.getSheetByName('Responses');
dataB_ID = sheetB.getRange('C2:C').getValues();
dataB_Status = sheetB.getRange('E2:E').getValues();
// Logger.log("DEBUG: C3 = "+dataB_ID[0][0]+", E3 = "+dataB_Status[0][0]);//DEBUG
var lastrow = sheetA.getLastRow()
// Logger.log("DEBUG: sheetA last row = "+lastrow);//DEBUG
// Flatten the array
var dataB_IDFlat = dataB_ID.map(function(row) {
return row[0];
});
//Loop through values on sheetA; check if they exist on sheetB
for (var i = 0; i < (lastrow - 1); i++) {
var A_ID = dataA_ID[i][0];
// Logger.log("DEBUG: id = "+A_ID);//DEBUG
// assign variable to return value index
var result = dataB_IDFlat.indexOf(A_ID);
if (result != -1) {
// it's there
// Logger.log("DEBUG: i: "+i+", ID: "+A_ID+", it's there"+", result#: "+result);//DEBUG
// Logger.log("DEBUG: Sheet1 status: "+data_Status[i][0]+" Vs Sheet2 status = "+dataB_Status[result][0]);//DEBUG
// compare status from sheetsA to sheetB
if (data_Status[i][0] != dataB_Status[result][0]) {
// Logger.log("DEBUG: status change to: "+dataB_Status[result][0]);//DEBUG
var range = sheetA.getRange(i + 2, 9);
//Logger.log("DEBUG: value = "+value);//DEBUG
//Logger.log("DEBUG: range = "+range.getA1Notation());//DEBUG
range.setValue(dataB_Status[result][0]);
}
} else {
// it's not there
// Logger.log("DEBUG: i: "+i+", ID: "+A_ID+", it's not there");//DEBUG
}
}
}
// Credit: Flatten array: https://stackoverflow.com/a/49354635/1330560

Can I assign an array value to a variable?

I can't seem to assign an array value to a variable. It always returns undefined.
In my code I have set currentWord = text[wordPos]. At the end of the code I have console logged currentWord, and text[wordPos]. My thinking says that they should return the same value, but they don't. currentWord returns undefined, and text[wordPos] returns the correct value (the first word in the 'text' array).
Solved. I had mistakenly forgot that I had 2 arrays, and thought the text array was not empty, but it was. The words array is the array I had filled in separate file.
var text = Array();
var wordPos = 0;
var currentWord = text[wordPos];
function gen() {
text = [];
var random;
for (var i = 0; i < 10; i++) {
random = words[Math.floor(Math.random() * 50)];
text.push(random);
}
document.getElementById('text').innerHTML = text.join(" ");
console.log(currentWord);
console.log(text[wordPos]);
}
Currentwork is undefined because you create an array object but never push a value into it. It transfers the current value of the variable not the reference.
There is no value at index 0 of text. If you assign some values to the text array you should be good!
Updated:
Read the OP's note above about the two arrays in the original example. In light of this information, the following script simulates an imported array words of 50 distinct values in order to generate a text of ten space-delimited numbers and indicate its first value:
// simulating an array imported from a separate file
var words = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50];
function gen() {
var wordPos = 0;
var currentWord = "";
var arr = [];
var randomVal;
var d = document;
d.g = d.getElementById;
var pText = d.g('text');
// get each of 10 values by randomly selecting an element's key
for (var i = 0; i < 10; i++) {
randomVal = words[ Math.floor( Math.random() * 50 ) ];
arr.push( randomVal );
}
pText.innerHTML = arr.join(" ");
currentWord = arr[wordPos];
console.log("Current word: ",currentWord );
}
gen();
<p id="text"></p>
This script randomly selects 10 numbers and adds them to an empty array by means of variable randomVal. This variable acquires a value in each iteration of the for-loop, during which the variable is passed to the push() method of arr in order to append it to the array. Once the loop terminates, the script joins the elements of arr on a blank space character, which yields a string whose numeric values are space-delimited.
One can discern that the script is working correctly when the console.log statement displays the first numeric value appearing in the text.

Google Apps Script counting number of identical cells in 2 columns

So Im have 2 columns in google spreadsheet, A and B, and I want to compare them row by row (A1 to B1, A2 to B2, etc), and finally count the number of cells that has the exact same value (can be a string or integer, but have to be identical) and put it in another cell, D1 for example. This is what I got so far, but it doesnt seem to do anything, and doesnt return any error either.
function compare() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('theSheetINeed');
var range1 = ss.getRange('A1:A'); //the first column
var data1 = range1.getValues();
var range2 = ss.getRange('B2:B'); //the second column
var data2 = range2.getValues();
var count = []; //to count the number of match
for(var i =0;i 'smaller than' data1.length; i++){ //somehow i cant use '<'
var abc = data1[i];
var def = data2[i];
if(abc == def){
count += count;
};
};
ss.getRange('D1').setValue(count.length);
}
Edit: so my code actually does something, it returns 0 everytime...
Modification points :
Values retrieved by getValues() are 2 dimensional array.
count is defined as an array. But it is not used as array.
i 'smaller than' data1.length is i<data1.length.
Starting row for column A and B are 1, 2, respectively.
Cells without values are included. So when such cells each other are compared, the values become the same. (If you want to compare such cells, please remove && abc && def from following script.)
Modified script :
Your script can be written by modifying above points as follows.
function compare() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('theSheetINeed');
var range1 = ss.getRange('A1:A'); //the first column
var data1 = range1.getValues();
var range2 = ss.getRange('B2:B'); //the second column
var data2 = range2.getValues();
var count = []; //to count the number of match
for(var i=0; i<data1.length-1; i++){ //somehow i cant use '<'
var abc = data1[i][0];
var def = data2[i][0];
if(abc == def && abc && def){
count.push(abc);
};
};
ss.getRange('D1').setValue(count.length);
}
If I misunderstand your question, I'm sorry.
An alternative to an Apps Script needing to run in the background you can do this with out-of-the-box formulas, and therefore the result is live
=SUM(
QUERY(
FILTER(A1:B,A1:A<>"",A1:A<>0),
"Select count(Col1)
Where Col1=Col2
Group By Col1
Label count(Col1) ''"
,0)
)
The advantage of formula based solutions is that they are more visible, and anyone following you can be sure the answer is correct without knowing they have to run a script to achieve this.
Breaking the formula down and starting in the middle:
FILTER(A1:B, A1:A<>"", A1:A<>0)
this returns all the rows where there is a non-empty cell. I do this because QUERY can be misleading with finding blank cells
"Select count(Col1)
Where Col1=Col2
Group By Col1
Label count(Col1) ''"
This does the comparisons you asked for and returns a count for each of the values in A that have a match in B. The Select here uses Col1 instead of A because the FILTER returns an Array not a
Range.
From then the SUM adds up each of those totals.

Categories

Resources