Google Apps Script Better Way to Get Unique Values - javascript

I have working code that takes data from two non-adjacent columns in a Google Spreadsheet, looks for unique values in the first column, and if unique creates a new array with the unique value from the first column and corresponding value in the second column. The problem is, the data I am using is already somewhat long (413 rows) and will only get longer over time. It takes about 1-2 minutes for the code to run through it. I've been looking for a shorter way to do this and I've come across the filter() and map() array functions which are supposedly faster than a for loop but I can't get them implemented correctly. Any help with these or a faster method would be greatly appreciated. The code I have right now is below.
function getkhanassignments(rows) {
var assignmentsraw = [];
var temparray = [];
var previousassignment = datasheet.getRange(50,1).getValue();
for(i=0, j=0;i<rows-1;i++) {
if(datasheet.getRange(50+i,1).getValue() != previousassignment) {
previousassignment = datasheet.getRange(50+i,1).getValue();
assignmentsraw[j] = new Array(2);
assignmentsraw[j][0] = datasheet.getRange(50+i,1).getValue();
assignmentsraw[j][1] = datasheet.getRange(50+i,8).getValue();
j++;
}
}
Logger.log(assignmentsraw);
return assignmentsraw;
}
The answers I've found elsewhere involve just getting unique values from a 1d array whereas I need unique values from a 1d combine with corresponding values from another 1d array. The output should be a 2d array with unique values from the first column and their corresponding values in the second column.

Solution:
The best practice of looping through ranges in Google Apps Script is to dump the range values into a 2D array, loop through that array, and then return the output array back to Google Sheets.
This way, there would be no calls to Sheets API inside loops.
Sample Code:
function getkhanassignments(rows) {
var assignmentsraw = [];
var table1 = datasheet.getRange(50,1,rows).getValues();
var table2 = datasheet.getRange(50,8,rows).getValues();
var previousassignment = table1[0][0];
assignmentsraw.push([table1[0][0],table2[0][0]]);
for(i=0; i<rows; i++) {
if (table1[i][0] != previousassignment) {
assignmentsraw.push([table1[i][0],table2[i][0]]);
previousassignment = table1[i][0];
}
}
Logger.log(assignmentsraw);
return assignmentsraw;
}
References:
Class Range
push()

Related

Filter function will not delete my empty rows - Google App Script

I want to import rows from one google sheet to the other, however source sheet imports a number of empty rows. Now I use a filter function to get rid of these rows but they will not disappear, can anyone tell me why?
var a = SpreadsheetApp.openByUrl("url").getSheetByName("Admin Use Only").getRange(4,1,6,21).getValues();
var b = SpreadsheetApp.getActive().getSheetByName('Credit_Detail');
b.getRange(b.getLastRow() +1, 1, a.length,21).setValues(a);
//filter function below:
var otarget=b.getRange(2,1,b.getLastRow()-1, 26).getValues();
var data=otarget.filter(function(r){
return !r.every(function(cell){
return cell === "";});
});
Logger.log(data);
b.getRange("A2:Z").clearContent();
b.getRange(3,1,data.length,data[0].length).setValues(data);
here's how I would do it. First, create an variable to store the array of the source. then run a for loop scanning the first column for empties. something like: for (var i = 0, i < data.length; i++) { if (data[i][0] != '') { XXXX } }
XXXX means that you can either put a code to create a new set of array which can be passed to the target sheet at once or use append row to transfer non blank rows to the target sheet one by one.
Note: Creating a new array to store non-empty rows would speedup the execution time if you are dealing with large data, thousands of rows.

Automatically separate and delimit a string in Google sheets

Google sheets has an option which is selectable from the top menu to separate text into columns when specifying a character. It's possible use a comma or other characters.
I am looking for a script which can do this process automatically for a given column. There are numerous scripts available to do this but I have not been able to accomplish my task using them.
I am using an application on Android which allows me to scan a qr code and send the string of information to Google sheets.
A sample of the information would appear as : 464839|362|2840|927|72|617
I need to separate this information into separate columns when the information is sent to sheets. The process should be automatic.
I have a snip of code which I've found searching however it doesn't work for me.
var range = SpreadsheetApp.getActiveRange();
var cell = range.getCell(1, 1); // Gets the cell A1
var cellValue = cell.getValue(); // Gets the value of the cell A1
var cellArray = cellValue.split("|"); // Splits the cell value by ',' and stores it in array.
//Iterate through the array
for(var i = 0; i < cellArray.length; i++){
Logger.log(cellArray[i]);
}
I'm not very code savvy, please help.
Below would be a code that you place on an installable trigger that runs at a regular interval and iterates through the values of each row in column A and tries to split the value with the pipe symbol and the replace then write those split values along the columns in that row. If this has already been done in the past the function will error when it tries to split the values because no pipe symbol exists but the try/catch will catch that error and allow the function to continue through the loop.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("NameOFSheet"); //Change "NameOFSheet to the actual name of the sheet.
var col1Values = sheet.getSheetValues(1, 1, sheet.getLastRow(), 1) //Gets 2d (2 dimensional) array of the Values in Column 1 (getSheetValues(startRow, startColumn, numRows, numColumns)_
var splitVals = []; //Instantiate an empty 1d array variable
//Iterate through the 2d array and set the values
for(var i = 0; i < col1Values.length; i++){
try{
splitVals = col1Values[i][0].split("|"); //Creates a 1d array of values split at every occurrence of the pipe symbol ("|").
//If there is no pipe symbol (which would be the case if this operation has already happened then the array will be blank because .split will throw an error which will get "caught" in the try catch and the row will be skipped
//Iterate over the splitVals array and set the values of the columns along the row (i+1) that you are in.
for(var col = 0; col < splitVals.length; col++){
sheet.getRange(i+1, col+1).setValue(splitVals[col])
}
}catch(e){}
}
}
I commented the code for explanation. I would recommend reading up on 2 dimensional arrays to help you understand them and the code above better.

Checking for existing array in array before pushing

I missing something when trying to push to an array while preventing duplicates.
I keep figuring out code that will push every occurence of an employee to the new employees array but I cannot figure out how to only push an unique list.
My final array is a 2d array so that can be setValues() back into a column in the Google sheet.
function queryEmployees(){
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var lRow = sh.getLastRow();
var data = sh.getRange(1,1,lRow,2).getValues();
var employees = [];
for(i=0;i<data.length;i++){
if(data[i][0]==='Team member evaluated'){
if(employees.indexOf([data[i][1]])===-1){
employees.push([data[i][1]]);
}
}
}
Logger.log(employees);
Logger.log(employees.length);
SpreadsheetApp.getActiveSpreadsheet().getSheets()[1]
.getRange(1,1,employees.length,1).setValues(employees);
}
IndexOf does not work with objects in arrays without your rewriting the function or writing your own. It works fine with strings, though. So a simple fix is to create a parallel array of strings, which allows us to keep your code almost intact. Thus, add,
var employeesIndex=[];
after your
var employees=[]
change the condition on your inner "if" clause to
(employeesIndex.indexOf(data[i][1])===-1)
and within that if block add a line to update the index
employeesIndex.push(data[i][1]);
That way the index tracks duplicates for you while your employees array contains arrays like you need.

JavaScript - Retrieve variable value generated inside the for loop, externally

Well, I am working with a rest API that fetch information from the same database, but two different tables:
Table #1 contains(Outputs JSON results that contains the below objects):
id
name
type
Table #2 contains(Outputs JSON results that contains the below objects):
id
value (real-time, updated periodically)
What I want to do is to compare the id in Table #1 and compare it to id
in Table #2 and if there is a match between the two id's outputs the sum of (value) from Table #2 with all the values having the same id.
Until now what I am able to do is the following:
File1.js
require('File2.js');
for (a in metricsData){
var metricsID = metricsData[a].id;
}
/* Certainly the below code is not working, but I don't know if it should
be done this way or it can be much more better.
Can't get the values of both metricsID & idFromLoad
if(metricsID === idFromLoad) {
var sum += value;
console.log('The new sum is : '+sum)
} */
File2.js
for(var i in load.updates){
var idFromLoad = load.updates[i].id;
var newVal = load.updates[i].value;
}
So, the big question is how to get the values of both metricsID & idFromLoad outside of the for loop? Is there a better way to achieve what I am trying to do?
What you are trying to accomplish is a little confusing, but to answer the question as described in the title of this post...
Because you have the variable defined inside the loop, it only exists inside the loop. If you want to "remember" the value outside of the loop, you would need to define it outside:
var metricsID;
for (a in metricsData){
metricsID = metricsData[a].id;
}
Note though, that outside the loop, it's value will equal the id of the last item in the loop.
If you are looking for an easy way to look up values in both tables, what you could do is loop through each once and create a map keyed off the id so that you can easily retrieve the value based on the id.
var updatesMap = {};
for(var i in load.updates){
var idFromLoad = load.updates[i].id;
updatesMap[idFromLoad] = load.updates[i].value;
}
Then in your other loop:
for (a in metricsData){
var metricsID = metricsData[a].id;
var updateValue = updatesMap[meticsID]; //this is the value from the other table
}

Problems with code listing unique items in a list and copying them into new worksheet

I'm trying to run the following code on a spreadsheet. The column of names is the 4th column. I'm attempting to run through that list of names, pushing each new unique name to an array (listOfNames), and then add a new worksheet (NamesList), and finally add the array to cell A1 of the new worksheet. When I run the code below, all I get is a blank popup with an OK and Cancel button. When I view the log, it is blank as well. I'm quite new at this, and I feel like I'm missing something obvious... just not sure what it is. Am I misunderstanding something specific to GAS rather than JS?
var sheet = SpreadsheetApp.getActiveSheet();
var listOfNames = new Array ();
function copyNames() {
var data = sheet.getDataRange().getValues();
for (i=0; i<=sheet.getLastRow(); i++){
var tempName = sheet.getDataRange(i,4).getValue();
for (i=0; i<=listOfNames.length; i++){
if (tempName != listOfNames[i]){
listOfNames.push(tempName);
logger.log(listOfNames);
}
}
}
sheet.insertSheet(ListOfEDs);
sheet.getRange('a1').setValue(listOfEDs);
}
Edit: I'm starting to see that this will push values multiple times to the list... so maybe it's just back to the drawing board all together. I have found other code that would create a list of unique elements, but wasn't really sure how that code worked. I thought I'd try to figure it out myself so I'd at least understand it.
EDIT 2: Ok... I tried some new code, but I'm still getting a blank message box, and nothing on the log. I wasn't sure if having i be the iterator for a for loop within a for loop was a bad thing, so I switched it to j. Also, I know there's the remove duplicates example, and have been looking at that, but am unsure why one needs to use join.
function copyNames() {
var sheet = SpreadsheetApp.getActiveSheet();
var listOfNames = new Array ();
var data = sheet.getDataRange().getValues();
for (i=2; i<=data.length; i++){ //starting at 2 because first row is a header
var tempName = data[i][4];
for (j=0; j<=listOfNames.length+1; j++){
if (tempName != listOfNames[j]){
listOfNames.push(tempName);
logger.log(listOfNames);
}
}
}
sheet.insertSheet("ListOfNames");
sheet.getRange('a1').setValue(listOfNames);
}
As far as I understand you went help instead of a ready solution, so here you go.
First:
Try to avoid using global variables. Keep all your variables inside of your functions. Otherwise you will have issues when you add more functions to your spreadsheet. Your Code should start like this:
function copyNames() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var listOfNames = new Array ();
Second:
Try to decrease the amount of service calls to spreadsheet. it takes several seconds for your script to go to your sheet and take the values. Instead of doing it each time, take these values once and push them in to a variable.
In your case, you already did that, however did not use that variable and kept using service calls. Check this line:
var data = sheet.getDataRange().getValues();
Now all of the data on that sheet is in a variable called data.
sheet.getDataRange(i,4).getValue();
is the same as
data[i][4];
The only difference is that in the first case it will take approximately 2 seconds to get that value, while in the second case only a few milliseconds.
Same goes for
sheet.getLastRow();
Either call it once and push it into a variable and use that, or in your case just use
data.length;
Third:
listOfNames is an empty array, so it's length is 0.
This line
for (i=0; i<=listOfNames.length; i++)
will not even run, as both i and listOfNames.length is 0.
That is why you logger does not give any output. Your script never get's to that line.
Fourth:
You do not have a variable called ListOfEDs, therefore your last two rows of code just give an error. Your script does not know what is ListOfEDs as it doesn't exist.
Hope this helps.

Categories