Google Apps Script find a value in an Array - google-apps-script

I can't figure out how to check if a value exists in an Array. I assumed this should be trivially simple, but can't get there.
I have this code:
function findPlayer() {
//This section gets the values from the first row of all the columns
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Players");
var lastColumn = ss.getLastColumn()
var playerNameRow = ss.getRange(1, 2, 1, lastColumn - 3);
var playerNameValues = playerNameRow.getValues();
//This just sets the value we're looking for (which in this example is in the 7th Column "G"). And logs the Array, just to check it.
var findPlayer = "Dan";
Logger.log(playerNameValues)
//and here we just loop through the playerNameValues array looking for the findPlayer value
for(var n in playerNameValues){
if(playerNameValues[n][0]== findPlayer) {break}
}
//and here, if the above search does find the matching value it should log it
Logger.log(n);
}
What's happening is the playerNameValue is logging correctly, but n is always logging as 0. Which implies it's finding the value in the first item it checks, rather than column 7 where the value is.

I notice you mentioned that the player is column 7. Note that what you are actually checking is
Row 1 Column 1
Row 2 Column 1
Row 3 Column 1
And never actually touch any columns apart from the first one because you have [0] in if(playerNameValues[n][0]== findPlayer) {break}. Instead do this (I assume you have var i and var n already)
for (i = 0; i < playerNameValues.length; i++) {
for (n = 0; n < playerNameValues[i].length; n++) {
if (playerNameValues[i][n] == findPlayer) break
}
if (playerNameValues[i][n] == findPlayer) break
}
we do a second break to break out of the second loop, but there are better ways of writing that code, this is just to ilustrate what you need to do.

Your rows and columns are reversed, use:
for(var n in playerNameValues[0]){
if(playerNameValues[0][n]== findPlayer) {break}
}
Or another way is to use indexOf instead of above.
Logger.log(playerNameValues[0].indexOf(findPlayer));

Related

how to get maximum index of array in appscript

I tried to get the values in a row into an array. For this I used the appscript in googlesheet. After checking the length of this rowtemp array, the answer is 1.
But I want to find the number of children inside. And if the element in "temp" is the same as the one in "rowtemp" then you need to find its column number
I used following code.
function rangeExa(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet4");
var temp = ss.getRange("A11:B22").getValues();
Logger.log(temp);
Logger.log(temp.length);
Logger.log(temp[3][0]);
var rowTemp = ss.getRange("D25:O25").getValues();
Logger.log(rowTemp);
Logger.log(rowTemp.length);
Logger.log(rowTemp[0][2]);
Logger.log(rowTemp);
for(i=0; i<=rowTemp.length; i++){
if(temp[3][0] == rowTemp[0][i]){
Logger.log("yessss");
}return;
}
}
As I explained in detail in the comment section, your goal is not clear.
However, there is a clear issue in the code an that relates to the length of rowTemp.
rowTemp is an array of a single array because it concers a single row. In other words, it has the format of [["High","Not High",..]]. Therefore, rowTemp.length will give you 1 since there is only one row in this array. If you want to iterate over the columns, you need to use rowTemp[0].length:
for(i=0; i<rowTemp[0].length; i++){
if(temp[3][0] == rowTemp[0][i]){
Logger.log("yessss");
Logger.log(i+4); // column number if there is a match
}
}
The above for loop will check if 36 appears in D25:O25 and it will output yessss if it does in the Logs page.
Also use i<rowTemp[0].length instead of i<=rowTemp[0].length because the last index of your array is rowTemp[0].length-1 otherwise you will get undefined.
Related:
What does the range method getValues() return and setValues() accept?

Copy pasting columns from one sheet to rows of another sheet with different count up steps in the for loop

I have one sheet: 'trainingmatrix' with information in row 2 columns B,D,F,H ...
which I would like to paste into another sheet 'vlearning' column C, rows 2,3,4,5.... I have tried the code below but it does not seem to work ...any ideas ? There should be a problem with the for loop but it's my first time doing a for loop with two variables and different count up steps.
var trainingmatrix=ss.getSheetByName('Training Matrix');
var vlearning=ss.getSheetByName('VLearning_Upload');
var lr=getlastrow(trainingmatrix,"A1:HA");
var tnavalues=trainingmatrix.getRange(1,1,lr-1,15).getValues();
var vlearnvalues=vlearning.getRange(1,1,vlearning.getLastRow(),
vlearning.getLastColumn()).getValues();
for (var i=1 && j=1;i<vlearnvalues.length && j<vlearnvalues[0].length;i++ && j=j+2){
vlearnvalues[i][2]=tnavalues[1][j];
}
Logger.log(values);```
Try looping with your two variables with two nested 'for' loops.
for (var i = 1; i < vlearnvalues.length; i++) {
for (var j = 1; j < vlearnvalues[0].length; j++) {
vlearnvalues[i][2] = tnavalues[1][j];
}
}
Although I would make sure your variables (vlearnvalues, tnavalues) contain what you think they do by logging them before looping over them because I'm not sure you're going to achieve what you want even with this loop working correctly.
I can see several issues in your code:
You have the wrong syntax for finding the last row. Please replace var lr=getlastrow(trainingmatrix,"A1:HA");through var lr = trainingmatrix.getLastRow();
As camaulay pointed out, you can not iterate over i and j simultaneously in one for loop, but rather have to nest the ā€œjā€ loop inside the ā€œiā€ loop.
Think about what you are doing: you are taking vlearnvalues[0].length (which is 15) and try to access the value tnavalues[1][j], which might not exist if the range width in Training Matrix is narrower than in VLearning_Upload.
Unfortunately vlearnvalues[i][2]=tnavalues[1][j]; does not work in Apps Script. Instead you need to access an individual cell within your range VLearning_Upload and use the function setValue() to assign it a value from the range in Training Matrix.
How do you choose that only certain values from the sheet Training Matrix are supposed to be copied? You need an if statement for it. E.g., assuming that B,D,F,H are non empty columns - and you want to copy only the non-empty values, you could implement: if(tnavalues[1][j]!="").
A sample code which meets your requirements would be this:
function Copy() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var trainingmatrix=ss.getSheetByName('Training Matrix');
var vlearning=ss.getSheetByName('VLearning_Upload');
var lr = trainingmatrix.getLastRow();
var tnavalues=trainingmatrix.getRange(1,1,lr-1,15).getValues();
var vlearnvalues=vlearning.getRange(2,3,vlearning.getLastRow(),vlearning.getLastColumn());
var array=[];
for (var j=1; j<tnavalues[0].length; j++)
{
if(tnavalues[1][j]!="")
{
array.push(tnavalues[1][j])
}
}
for(var i=1;i<vlearnvalues.getHeight();i++)
{
var cell=vlearnvalues.getCell(i,1);
if(typeof array[(i-1)]!=='undefined')
{
cell.setValue(array[(i-1)]);
}
}
}
Please refer to the Apps Script reference to find more information about different methods and examples of how to use them:
https://developers.google.com/apps-script/reference/

Find rows with particular value from a Google Spreadsheet without using loop?

I am new to spreadsheet scripting. I am generating a report (new sheet) based on another sheet where I enter values daily. In Apps Script I first generate the sheet then loop through the data range retrieved from that input sheet.
After that I have to merge values based on dates and categories.
Now my report format is such that rows are categories and dates are columns.
So if in input if there is another value with same date and same category I have to add the value.
My problem is how to check if the value with same date and category exists in the report and I DO NOT want to use loops as I am already in loops so that will make the process run very very slow.
I don't think it is possible to do it without some looping. Since this operation is carried out server side without the need to make calls to the spreadsheet it would take a very small amount of time even with a very large dataset.
If your script is already slow it more than likely because of inefficiencies/ delays in some other part of the script. I have a script which duplicates a spreadsheet and renames it, just those to operations take between 5 & 8 seconds.
As an example:
function test(){
var ss = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var value = "agdsdfgsdfg" // This value is in cell BD1000
for(var i = 0; i < ss.length; i ++){
if(ss[i].indexOf(value)>=0){
var x = ss[i].indexOf(value) + 1;
break;
}
}
var y = i + 1
// var x & y are the row, cell coordinates of value in the data range
}
This operation carried out on a dataset 56 columns x 1000 rows completes in 0.88 seconds with the search value in the last cell of the range.
Your report sounds a fair bit like a Pivot Table with categories in rows, dates in columns, and SUM(Value) as the data field. To reproduce this with a script report, you can use an Object variable that maps between a key and a "value" Object:
This probably isn't your exact use case (it assumes you need to generate a new report from a possibly-large stack of feeder data, but it should demonstrate how you can use nested Objects to simplify / internalize the lookup process, including testing for undefined values and enforcing a rectangular output.
// Make an array of the data, to limit use of the slow spreadsheet interface.
var inputs = SpreadsheetApp.openById(<id>).getSheetByName(<dataSheetName>)
.getDataRange().getValues();
// The first row is probably column headers from the input sheet, and
// doesn't likely contain useful data that you want in your report.
var headers = inputs.splice(0, 1);
var report = {};
for(var row = 0; row < inputs.length; ++row) {
// Change these indexes (0, 1, 2) to the proper values.
// Also do any necessary formatting / validation, etc. for "category" and "date".
var category = inputs[row][0];
var date = inputs[row][1];
var value = inputs[row][2];
// If this category doesn't exist, default construct its report object.
// For each category, a nested object is used to store the date-value pair.
if(!report[category]) {
report[category] = {};
}
// Otherwise, if the date is not yet seen for the category, set
// the value. If it is seen, increment the stored value by the new value.
if(!report[category][date]) {
report[category][date] = value;
} else {
// Treat this as numeric addition, not string concatenation.
report[category][date] += value - 0;
}
}
// To print your report, you need a header you can index against.
var outputHeader = [];
for(var category in report) {
for(var date in category) {
outputHeader.push(date);
}
}
// Sort this header row. If the dates are strings that don't simply
// coerce to proper Date objects, you'll need to write your own sort() method.
// (You don't technically need to sort, but if you don't then the dates
// won't be "in order" when the report prints.)
outputHeader.sort();
// After sorting, add a row label for the header of sorted dates.
outputHeader.splice(0, 0, "Category / Date");
// Serialize the report object into an array[][];
var output = [outputHeader];
var totalColumns = outputHeader.length;
for(var category in report) {
// Initialize each row with the row label in the 0 index position.
var row = [category];
for(var date in category) {
var index = outputHeader.indexOf(date);
row[index] = category[date];
}
// Unless you are guaranteed that every category has a value for every date
// in the report, you need to ensure that the row has a value at each index.
// (This is a good idea anyway, to ensure that you have a rectangular array.)
var filled = Object.keys(row);
// We can start at 1 since we know that every row starts with its category.
for(var col = 1; col < totalColumns; ++col) {
if(filled.indexOf(String(col)) < 0) {
row[col] = "";
}
}
output.push(row);
}
SpreadsheetApp.openById(<id>).getSheetByName(<reportSheetName>)
.getRange(1, 1, output.length, output[0].length).setValues(output);

Cannot read property from undefined

I have the following code, which works fine and does what it's supposed to do.
However my table has 2 rows at the top which doesn't interest me (they wouldn't match the if clause anyway so it doesn't affect me, just trying to figure this out) so I was trying to tailor my range to simply exclude them.
All I did was change the A1 to A3 inside the getRange and it's throwing the Cannot read property "1" from undefined (referring to the hardcoded 1 in the if statement).
The reason I don't understand why it's undefined is because changing the range like this shouldn't affect it at all, since I'm reducing my range from A1:B6 (6 rows in my sheet with the first 2 being either empty or not needed) to A3:B6, but the first value in the set (A3) should still end up at [0][0] inside 'data'.. unless getValues() messes something up that I don't know about..
var lastRow = sheet.getLastRow();
var myRange = sheet.getRange("A1:B" + lastRow);
var options = new Array();
var data = myRange.getValues();
for(var i = 0; i < lastRow; i++) {
if(data[i][1] == region)
{
options.push(data[i][0]);
}
}
Thanks!
Your bug stems from your for loop; it expects to iterate over 6 rows (i starts at 0 and increments by 1 up to 5), when what you really want is to iterate over 4 rows matching the range A3:B6. So the array only has 4 elements but your for loop tries to reference a 5th element that does not exist.
Simplest solution is to use the Array object's built in array methods (I'm using forEach in this case) to iterate over the items specifically in the range you defined as follows:
var range = sheet.getRange("A3:B"), // you automatically reference the last row with A3:B, no need for A3:B6
options = [],
region = '[some-region-value]';
range.getValues().forEach(function(row){
row[1] == region && options.push(row[0]); // exploit short-circuit mechanism in logical AND operator (in this case the operand on the right, the push, only gets executed if the operand on the left is true)
});
Array indices start with zero. The index of the last element is length of an array (total number of elements) - 1. Modify your 'for' loop like this and it will work
for(var i = 0; i < data.length; i++)
As the previous poster pointed out, you iterate from 0 to what the function getLastRow() returns. Remember, it returns the last row with data in a sheet. It has nothing to do with your array.

Autofill google forms based on user input

Alright stack friends,
I'm working on my first projects using google scripts and it's been pretty fun so far. My project is to create a form for data entry that can either accept an ID number and fill in the rest of the fields, or let the user fill out the entire form. Basically my method to fill in the other fields is just to have a lookup table on the second sheet. When the user submits a form, the script runs, looks for the ID of the last row, scans the reference table for the ID, and then fills in the details.
I think the problem I'm having is the assumption that the data from the form is already in the sheet when the script runs. The problem I noticed is that the script sometimes fails to fill in the gaps. I tried creating form submissions in a loop with the same ID and they function somewhat erratically but it seems like the last sumbission always works which would make sense if the script executions are not matching up with the form submissions. Here's the script for reference:
function fillGaps() {
// First take in the appropriate spreadsheet objects and get the sheets from it
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheets()[0];
var refSheet = ss.getSheets()[1];
// Here's the last rows' index
var lastRow = sheet.getLastRow();
var lastRowRef = refSheet.getLastRow();
// now this is an array of values for the last row and the student ID entered
var response = sheet.getRange(lastRow, 1, 1, 7).getValues();
var enteredID = response[0][1];
// Next we're going to try to load up the lookup table and scan for the ID
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
var row = 0;
while(enteredID != stuIDs[row] && row <= lastRowRef){
row++;
}
// Okay at this point the row variable is actually -2 from what the sheet index
// is that I'm thinking of. This is because we didn't load the first row (names)
// and the way arrays are indexed starts with 0.
row++;
row++;
// now assuming that it found a match we'll fill in the values
if(row < refSheet.getLastRow()){
// Alright now we need to wrangle that row and format the data
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
}
So I'm wondering:
Does this seem like the right diagnosis?
If so, what would be the best way to remedy? I thought of adding a little delay into the script as well as trying to capture the submissions timestamp (not sure how to do that)
Thank you much!
The following code gives a 2D array:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
Also,refSheet.getLastRow gives the last row, lets say it is 10 in this case. The syntax for getRange is getRange(row, column, numRows) and the last argument is the number of rows, not the last column. So in the above code the selected range would be row 2 - 11 rather than 2- 10. Unless that is what you intended, modify the code like so:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()-1).getValues();
To access the values in stuIDs you should use stuIDs[row][0] (2D array) to check for matching ID. Assuming your ID was to be matched was in column 1.
Secondly, in the loop you are using the following to check for the last index in array row <= lastRowRef which will cause it go out of range(because array starts at 0 and sheet row at 1) instead use this row < stuIDs.length
Finally, in case you don't find a match you will end up with the last row and your code will end you taking the last row as the matched index. This can be prevented by using a boolean variable to check for a match.
var foundId = false
var row = 0;
var i = 0;
for (i in stuIDs){
if(stuIDs[i][0] == enteredID)
foundID = true
break
}
}
row = i + 2
if (foundID){
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
PS: You can also use event objects to get the values of response (eventObj.values). As mentioned here: https://developers.google.com/apps-script/guides/triggers/events
Hope that helps!