Get row & column indices of found value - google-apps-script

How do I get the row and column indices of the cell containing a value I'm looking for?
Here's an example of two sheets, "Grave" and "Data_grave":
My code, below, should...
First, get a specific value in sheet "Grave" (in example value is - "Win").
Find the number of the row & column with this value in sheet "Data_grave".
Finally, it should write some data ("wow") near the found value "Win" (from column+1).
However, I receive an error message at line 17 (the line following my search loops):
Can't convert 4,4 to (class)
How do I solve that?
function myFind() {
var ss = SpreadsheetApp.getActive(), rowNum = [], collNum = [];
var findData = ss.getSheetByName('Grave').getRange("A2").getValue();
var searchData = ss.getSheetByName('Data_grave').getDataRange().getValues();
for(var i=1, iLen=findData.length; i<iLen; i++) {
for(var j=0, jLen=searchData.length; j<jLen; j++) {
for(var k=0, kLen=searchData[0].length; k<kLen; k++) {
var find = findData;
if(find == searchData[j][k]) {
rowNum.push([j+1]);
collNum.push([k+2]);
}
}
}
}
ss.getSheetByName('Data_grave').getRange(rowNum,collNum).setValue("wow");
}

As Adelin commented: the error message is indicating that you are not using .getRange(rowNum,collNum) properly. That method expects two numbers, but you're providing it two arrays.
When you've "found" the cell you're searching for, instead of push() (which treats rowNum and colNum as arrays), you simply want to use:
var rowNum = j+1;
var colNum = k+2;
You could also use a boolean found as an additional exit condition for all your loops, to stop searching upon success.
function myFind() {
var ss = SpreadsheetApp.getActive(), rowNum = [], collNum = [];
var findData = ss.getSheetByName('Grave').getRange("A2").getValue();
var searchData = ss.getSheetByName('Data_grave').getDataRange().getValues();
var found = false;
for(var i=1, iLen=findData.length; i<iLen && !found; i++) {
for(var j=0, jLen=searchData.length; j<jLen && !found; j++) {
for(var k=0, kLen=searchData[0].length; k<kLen && !found; k++) {
var find = findData;
if(find == searchData[j][k]) {
var rowNum = j+1;
var collNum = k+2;
found = true;
}
}
}
}
ss.getSheetByName('Data_grave').getRange(rowNum,collNum).setValue("wow");
}

Related

Running script for multiple search terms found on different sheet

I'm trying to get a SCORESHEET to populate from a REPORTSHEET, using a REFERENCESHEET to collate search terms and destination cells.
The script I'm running is as below. The idea is that the script finds searchDate's in the REFERENCESHEET and uses them to locate data columns in the REPORTSHEET:
function superAuto() {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var scorecard = SpreadsheetApp.openById('SCORESHEET');
var scorecardData = scorecard.getDataRange().getValues();
var tExpenses = "Total Expenses";
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
}
var column = columnfinder(searchDate);
for (var a = 0; a < referenceData.length; a++) {
var refRow = referenceData[a];
for (var i = 0; i < reportData.length; i++) {
var row = reportData[i];
if (row[0] == tExpenses && refRow[0] == searchDate) {
scorecard.getRange(refRow[5]).setValue(row[column]);
}
}
}
}
function columnfinder(find) {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
for(var j=0, jLen=reportData.length; j<jLen; j++) {
for(var k=0, kLen=reportData[0].length; k<kLen; k++) {
if(find == reportData[j][k]) {
Logger.log(k);
return (k);}
}
}
}
Broadly speaking, the code works, as if I define searchDate as one of the terms I'm looking for (e.g. Jan-21) it all works fine. The issue is that it doesn't seem to be doing so when finding multiple search terms - and therefore populating multiple rows - as per:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
}
The log tells me that it's finding searchDate's in the REFERENCESHEET, but it's not able to run them through function columnfinder (I get no logs for the second logger).
I suspect the answer lay somewhere in an earlier great answer I received to an earlier version of this idea - How to return multiple column values for setValue - but I've not been able to make it fit. Any thoughts?
EDIT: Please find a sample REFERENCESHEET & REPORTSHEET for more info:
The log tells me that it's finding searchDate's in the REFERENCESHEET,
but it's not able to run them through function columnfinder (I get no
logs for the second logger)
You don't execute columnfinder inside the for loop.
Try this:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log(searchDate);
columnfinder(searchDate); // modified code
}
and you will get both logs.
Sorry if I misunderstood your question.
You need to use have it assigned to array since you are returning possible multiple columns/dates:
function superAuto() {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var scorecard = SpreadsheetApp.openById('SCORESHEET');
var scorecardData = scorecard.getDataRange().getValues();
var tExpenses = "Total Expenses";
var searchDates = [];
for (n = 0; n < referenceData.length; ++n) {
searchDates.push(referenceData[n][0])
}
var columns = columnfinder(searchDates);
columns.forEach(function (column, index) {
referenceData.forEach(function (refRow) {
reportData.forEach(function (row) {
if (row[0] == tExpenses && refRow[0] == searchDates[index]) {
scorecard.getRange(refRow[5].toString()).setValue(row[column]);
}
});
});
});
}
function columnfinder(dates) {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var columns = [];
dates.forEach(function (date) {
reportData.forEach(function (row, i) {
row.forEach(function (col, i) {
if (date == reportData[i][j]) {
columns.push(reportData[i][j]);
}
});
});
});
return columns;
}
I changed some variables into proper variable names to avoid confusion.
Additionally, if it doesn't work, you might need to share a proper visualization of the data, or better yet, provide some sample sheet we can work on for us to be able to give you a better and tested answer.
Thanks Marios, that's twice in a week. Much appreciated.
Slight adaptation, in order for it to populate SCORECARD I needed to bring everything into the for loop, as below:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
var column = columnfinder(searchDate);
for (var a = 0; a < referenceData.length; a++) {
var refRow = referenceData[a];
for (var i = 0; i < reportData.length; i++) {
var row = reportData[i];
if (row[0] == tExpenses && refRow[0] == searchDate) {
scorecard.getRange(refRow[5]).setValue(row[column]);
}
}
}
}

AInternal Error executing custom function

Is there any limitation in Apps Script?
I'm getting "internal Error" if I set variable "lLen" more then 18 - http://prntscr.com/j60kxb
Actually I need to have this string as var lLen = CIDlist.length; but I'm getting the Error above. In some cases CIDlist.length value can be 160+. When I played to understand the reason of the issue I found that it works if lLen <= 18. Any ideas why it happens?
function myFunction(input) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheet = sheets[3];
var lastrow = sheet.getLastRow();
var CIDlist = [];
for(var i =16; i<=lastrow; i++){
var firstval = sheet.getRange("B"+i).getValue();
var secondval = sheet.getRange("C"+i).getValue();
if (firstval == input[0][1] && secondval == input[0][0]) {
var CID = sheet.getRange("A"+i).getValue();
if (CIDlist.indexOf(CID) == -1) {
CIDlist.push(CID);
}
}
}
console.log(input);
console.log(CIDlist.length);
var lLen = 19;
var TotalRevenue = 0;
for (var i=0; i< lLen; i++){
var CIDvalue = CIDlist[i];
for (var j=16; j<=lastrow; j++){
var cid = sheet.getRange("A"+j).getValue();
var revenue = sheet.getRange("D"+j).getValue();
if (cid == CIDvalue) {
TotalRevenue = TotalRevenue + revenue;
}
}
}
return TotalRevenue;
}
The function on the question makes use of two for loops that makes several calls to the SpreadsheetApp classes.
As it fails when is increased a value that control the number of iterations of one of the for loops, which makes that the time execution of the custom function be increased, it's very likely that it's exceeding the 30 second limit for custom functions.

How to pass data from one column to another one ?

I have a spreadsheet with multiple rows and columns. Two columns (column 3&4) are filled with text. I want to clean the text from this two columns and delete every specific characters (newlines, comma, exclamation point, quote,etc...). So I wrote the following script :
function testwoD() {
var input = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Raw_data");
var output = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Raw_data");
var row_count = input.getLastRow()
var col_count = input.getLastColumn();
raw_data = input.getRange(1, 1,row_count,col_count).getValues()
temp3 = []
for (var i = 0; i < row_count; i++) {
var punctRE = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?#\[\]^_`{|}~\r\n|\n|\r]/g;
var spaceRE = /\s+/g;
temp3.push(raw_data[i][4].toString().replace(punctRE, '').replace(spaceRE, ' '));
}
temp4 = []
for (var i = 0; i < row_count; i++) {
var punctRE = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?#\[\]^_`{|}~\r\n|\n|\r]/g;
var spaceRE = /\s+/g;
temp4.push(raw_data[i][3].toString().replace(punctRE, '').replace(spaceRE, ' '));
}
var toAddArray3 = [];
for (i = 0; i < temp3.length; ++i){
toAddArray3.push([temp3[i]]);
}
var toAddArray4 = [];
for (i = 0; i < temp4.length; ++i){
toAddArray4.push([temp4[i]]);
}
output.getRange(1, col_count-13,row_count,1).setValues(toAddArray3);
output.getRange(1, col_count-14,row_count,1).setValues(toAddArray4);
}
It's working but It's very complicated and confusing. I made it step-by-step so even myself have some difficulties to really explain it.
Is there a way to significantly improve it ?
Best,
Simon.
DRY! - Do not repeat yourself.
Another popular idiom should be UMNF - Use map not for.
Putting everything into its own function encapsulates functionality and puts the focus on what you want to do with the data at each level rather than bookkeeping indices and subscripts.
function cleanColumns() {
var input = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var output = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var raw_data = input.getDataRange().getValues();
var columnsToClean = [3,4];
function cleanText(t) {
var punctRE = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?#\[\]^_`{|}~\r\n|\n|\r]/g;
var spaceRE = /\s+/g;
return t.toString().replace(punctRE, "").replace(spaceRE, " ");
};
function cleanColumn(col) {
return raw_data
.map(function(row) {return row[col];})
.map(cleanText)
.map(function(row) {return [row];})
};
function cleanAndWrite(col) {
var data = cleanColumn(col);
output.getRange(1, col + 1, data.length, 1).setValues(data);
}
columnsToClean.forEach(cleanAndWrite);
}

Optimizing google apps script for replacing/appending values

I have a function that loops through array C:D to find a match in A:B, if there is it replaces the value in B with D and if there's no match it appends C:D to A:B. This function is using loops. I know there's a way to optimize this, but I'm lost. How else can this script run without loops?
function moveValues() {
var ss = SpreadsheetApp.openById('open_id');
var source = ss.getRange('sheet2!D:C');
var destination = ss.getRange('sheet2!A:B');
var destCount = 0;
for (var j = 1; j <= destination.getLastRow(); j++) {
if (destination.getCell(j,1).getValue() == "") {
destCount = j;
break;
}
}
for (var i = 1; i <= source.getLastRow(); i++) {
Logger.log(source.getLastRow());
var added = false;
var targetName = source.getCell(i,1).getValue();
var copyValue = source.getCell(i,2).getValue();
if (targetName == "") {
break;
}
for (var j = 1; j <= destCount; j++) {
var curName = destination.getCell(j,1).getValue();
if (copyValue != "" && targetName == curName) {
destination.getCell(j, 2).setValue(copyValue);
added = true;
break;
}
}
if (!added) {
destination.getCell(destCount, 1).setValue(targetName);
destination.getCell(destCount, 2).setValue(copyValue);
destCount += 1;
}
}
source.clear();
};
You will still need to use loop(s), but the code can be optimized. Use getValues() at the beginning. That returns a 2D array. You can use .indexOf() to determine whether there is a match in the other array.
function moveValues() {
var i,L,sh,ss,srcRng,destRng,srcData,targetData,v;
ss = SpreadsheetApp.openById('open_id');
sh = ss.getSheetByName('sheet2');//Get sheet2
lastRow = sh.getLastRow();//Get the row number of the last row
srcRng = sh.getRange(1,1,lastRow);//Get the range for all the values in column 1
destRng = sh.getRange(3,1,lastRow);//Get the range for all the values in column 3
srcData = srcRng.getValues();//Get a 2D array of values
targetData = destRng.getValues();//Get a 2D array of values
srcData = srcData.toString().split(",");//Convert 2D to 1D array
targetData = targetData.toString().split(",");//Convert 2D to 1D array
L = srcData.length;
for (i=0;i<L;i++) {//Loop the length of the source data
v = srcData[i];//Get this value in the array
if (targetData.indexOf(v) !== -1) {//This value was found in target array
}
}
This is not a complete answer to your question, but hopefully it will give you some ideas.
In this example the code is getting just the columns of data to compare, and not the columns of data to change.

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.