1

First-time poster here. I would like some insight on some Google App Script code i think could be spruced up a bit.

Long story short.

I have a 2 Google Sheet tables

  • A “LOCALE” spreadsheet - listing unique names of locations

  • A “FEED” spreadsheet - listing photo descriptions, including the locations. The same location is listed multiple times in this spreadsheet.

  • Both of these tables have a “Location” column, which references each other with a Key column.

The problem I want to solve for:

When I edit a location name in the “LOCALE” spreadsheet, it should automatically update all the location names in the “FEED” spreadsheet.

The way I solved this problem:

I used a for loop within a for loop for this. To summarize:

  • for every row in "LOCALE"...
  • ..go through every row in "FEED"...
  • ...If a value in the Key column in the FEED Sheet matches a value in the Key column in the LOCALE Sheet...
  • ...but the value in the Location column in the FEED Sheet doesn't match the value in the Location column in the LOCALE Sheet...
  • ...update the Location column in the FEED Sheet with the value in the Location column in the LOCALE Sheet.

If you're not confused yet, here's the code i wrote for it:

// for each row in the "Locale" sheet... 
for(var L = LocationsRefValues.length-1;L>=0;L--) {

  // for each row in the "Feed" sheet...
  for(var F = FeedRefValues.length-1;F>=0;F--) {

    if (FeedRefValues[F][97] == LocationsRefValues[L][17] && 
        FeedRefValues[F][10] != LocationsRefValues[L][1]) {
       FeedDataSheet.getRange(F+2,10+1).setValue(LocationsRefValues[L][1]);
    }       
  }
}

Now, this code works perfectly fine, I've had no issues. However, i feel like this a bit clunky, as it takes a while to finish its edits. I'm certain there's any easier way to write this and run this code. I've heard arrays may address this situation, but i don't know how to go about that. Hence, why I'm looking for help. Can anyone assist?

Keep in mind I'm a total Google App Script beginner who got this code working through sheer dumb luck, so the simpler the solution the better. Thanks for any consideration to my problem in advance. Looking forward to hearing from you all.

This is the full function (after i made edits suggested here.)

   function ModeratorStatus() {

  var Data = SpreadsheetApp.getActiveSpreadsheet(); // Local Spreadsheet

  var ModeratorStatusDataSheet = Data.getSheetByName("The Status (Moderators)");
  var ModeratorStatusRange = ModeratorStatusDataSheet.getRange("A2:C");
  var ModeratorStatusRefValues = ModeratorStatusRange.getValues();

 var ModeratorDataSheet = Data.getSheetByName("The Moderator_Numbers");  // DATA "Member" sheet
  //var ModeratorRefValues = ModeratorDataSheet.getRange("A2:AD").getValues();

  var ModeratorStatusObj = {};
for (var MOS = ModeratorStatusRefValues.length-1; MOS>=0; MOS--) {
  ModeratorStatusObj[ModeratorStatusRefValues[MOS][2]] = ModeratorStatusRefValues[MOS][0];
}
  var ModeratorValues = ModeratorDataSheet.getRange("A1:AD").getValues();
for (var MO = ModeratorValues.length-1; MO >=0; MO--) { // for each row in the "Moderator" sheet...
  var ModeratorVal28 = ModeratorValues[MO][28];
  if (ModeratorStatusObj[ModeratorVal28] != ModeratorValues[MO][1]) {

    ModeratorValues[MO][1] = ModeratorStatusObj[ModeratorVal28];
  }
}
var destinationRange = ModeratorDataSheet.getRange(1, 1, ModeratorValues.length, ModeratorValues[0].length);
destinationRange.setValues(ModeratorValues);

I used the code in a different function as a test. To make it easier

LOCALE = MODERATOR STATUS

FEED = MODERATOR

Donatello
  • 13
  • 3
  • 1
    By `for(var C =`, is that a typo, did you mean `for(var L =`? – CertainPerformance Jan 09 '20 at 06:05
  • Have you considered to use find & replace? https://stackoverflow.com/questions/26480857/how-do-i-replace-text-in-a-spreadsheet-with-google-apps-script – Trimax Jan 09 '20 at 09:31
  • yep, that was a during my copy/paste. Thanks! @CertainPerformance – Donatello Jan 09 '20 at 19:35
  • `ModeratorValues[MO + 2][1 + 1]`: What does this mean? Arrays start at 0 and don't need offset: `ModeratorValues[MO][1 + 1]` – TheMaster Jan 09 '20 at 22:01
  • @TheMaster Embarrassing quirk, but I offset my both my row and column arrays by positions by 1, since I have to call the actual position in the script sometimes, I get confused whether i calling the array or range position. I just one the +1 has a handicap so i can copy-paste easier. So for me, I'll write out range Array ModeratorRefValues[MO][**1**] as getRange(MO,**1**+1) so i can put the same digit in both places & know it's calling the same value. the `[MO + 2]` array starts at the second row of my sheet (to avoid touching the headers which my older code was doing. – Donatello Jan 09 '20 at 23:43
  • @TheMaster i fixed my offset error. But now getting a "cannot set property" error. Seems one of the values is not declared? I edit my posts with the code and screenshots of the error – Donatello Jan 09 '20 at 23:53
  • Like I said in my previous comment, it should be `ModeratorValues[MO][1 + 1]` Arrays start at 0. You're modifying the array and not the sheet. Say if `A2:AD5` range is received as `values`=> values[0][0] will be A2. values.length will be 4(row2 to 5). length-1 = 4-1 =3.``values[3+2]`` = values[5] will be `undefined`(no 6th row in this array{it only has 4 rows at index: 0,1,2,3}).`values[5][1+1]`=>values[5][2]=> undefined[2]=> cannot set property 2 of undefined. Do you see? – TheMaster Jan 10 '20 at 07:07
  • 1
    Do not continually edit the question with new code to try to get help resolving a different error. If one of the answers provided a useful solution to the original question, accept it and then [ask a new, *good* question](https://stackoverflow.com/help/how-to-ask) (and reference this one in it). – tehhowch Jan 10 '20 at 14:57
  • @TheMaster still a bit confused, but i understand it better now. I was using the address for the sheet, and not the array. I've since fixed that (but i had to change the getRange value to work like i needed it to. Thanks for the help there. However, Now the code is changing the header value tool. It's confusing me because i wrote my original code to omit the header row. Can you provide insight into how i can modify the code to avoid changing the header row? – Donatello Jan 10 '20 at 14:59
  • I'd have to agree with tehhowch. A new question focused on new error would be better. Having said that, why not just `getRange("A2:AD")` and `ModeratorDataSheet.getRange(2, 1, ModeratorValues.length, ModeratorValues[0].length); destinationRange.setValues(ModeratorValues);`? If you aren't able to make it work, ask a new question. – TheMaster Jan 10 '20 at 16:01
  • @tehhowch Sincere apologies guys, As i said before, first-time poster. I'm not aware of the proper decorum here. Sorry for any inconvenience. That said, my error(s) were based on the suggested code, so the problem wasn't yet solved to my mind. However, the last suggestion in this thread works, so I'm all set. I'll mark the comments accordingly. I appreciate the patience and response. – Donatello Jan 10 '20 at 16:30

2 Answers2

0

If there are no duplicate [17]s with different [1]s in the LocationsRefValues, you can reduce the computational complexity from O(n ^ 2) to O(n) by creating a mapping object for LocationsRefValues beforehand, whose keys are the LocationsRefValues[L][17]s and whose values are the LocationsRefValues[L][1]s:

var locationObj = {};
for (var L = 0; L < LocationsRefValues.length; L++) {
  locationObj[LocationsRefValues[L][17]] = LocationsRefValues[L][1];
}
for (var F = FeedRefValues.length - 1; F >= 0; F--) { // for each row in the "Feed" sheet...
  var feedVal97 = FeedRefValues[F][97];
  if (locationObj[feedVal97] != FeedRefValues[F][10]) {
    FeedDataSheet.getRange(F + 2, 10 + 1).setValue(locationObj[feedVal97]);
  }
}

Thanks @TheMaster, you can speed this up by calling setValue only once, at the end, rather than calling it in a loop, probably something along the lines of:

var locationObj = {};
for (var L = 0; L < LocationsRefValues.length; L++) {
  locationObj[LocationsRefValues[L][17]] = LocationsRefValues[L][1];
}
var feedValues = FeedDataSheet.getValues();
for (var F = FeedRefValues.length - 1; F >= 0; F--) { // for each row in the "Feed" sheet...
  var feedVal97 = FeedRefValues[F][97];
  if (locationObj[feedVal97] != FeedRefValues[F][10]) {
    feedValues[F + 2][10 + 1] = locationObj[feedVal97];
  }
}
var destinationRange = ss.getRange(1, 1, feedValues.length, feedValues[0].length);
destinationRange.setValues(feedValues);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 1
    @Dona In addition, avoid `setValue()` in a loop. See [this](https://stackoverflow.com/a/57836700/). You should rather recreate the entire modified table as a 2D array and use `setValues()` in one shot. – TheMaster Jan 09 '20 at 07:38
  • @TheMaster getting an error at this line feedValues[F + 2][10 + 1] = locationObj[feedVal97]; Cannot set property "2.0" of undefined to "former mods that no longer supports the organization, possibly due to bad terms". (line 2214, file "Context App Code") [0.533 seconds total runtime] Also, had to change one of the brackets, looks like it was incorrectly entered – Donatello Jan 09 '20 at 16:18
  • @Dona Edit your question to show the latest code and the error – TheMaster Jan 09 '20 at 17:39
  • @TheMaster I've edited my question with the modified code and the error. Any thoughts? – Donatello Jan 09 '20 at 21:34
  • @TheMaster Also, the error indicates It' s comparing one cell to the right of the Name column in the Moderator Status (the B column, instead of the A column). The text string being shown is in the 2nd column when i just one change the value in the name column (the first one) – Donatello Jan 09 '20 at 21:57
  • @Dona Continue to play with it until you get the correct offset. Arrays start at index 0. – TheMaster Jan 09 '20 at 22:10
0

you can use onEdit(e) trigger to get the reference to the edited cell. In that case you won't need to iterate over the entire Locale" sheet:

function onEdit(e) {
  var range = e.range; // edited cell
  var rowIndex = range.getRow()
  var colIndex = range.getColumn()
  if (rowIndex >= LocaleRange.startRow && rowIndex <= LocaleRange.EndRow && 
      colIndex >= LocaleRange.startColumn && colIndex <= LocaleRange.EndColumn) {
    var index = rowIndex - LocaleRange.startRow
    var keyValue = LocationsRefValues[index][17]
    var newLocValue = range.getValue()
    var newFeedValues = FeedRefValues.map(function (row) {
       return (row[97] == keyValue)  newLocValue ? : row[10]
    })
    FeedDataRange.setValues(newFeedValues)
  }
}

Here are docs on using onEdit trigger: https://developers.google.com/apps-script/guides/triggers

Дмитро Булах
  • 3,697
  • 1
  • 14
  • 23
  • Thanks for the swift response @Дмитро Булах! Unfortunately, The onEdit function won't work for my needs. To make my explanation brief, I failed to mention that I'm using a 3rd party app to edit the Google Spreadsheet. The app's edits do not trigger the onEdit function, so I have to run my script periodically using a timed trigger. With that in mind, can this script be modified to be used within a time-based trigger? – Donatello Jan 09 '20 at 14:37
  • If you can't use `onEdit` trigger then you'll have to loop through all the rows and cannot optimize this. In that case, the only significant improvement you could get is using `setValues` for bulk updates – Дмитро Булах Jan 09 '20 at 17:18