I have dozens of spreadsheets thousands of rows long and I want to subset them by deleting rows that do not satisfy a condition.
Let me put forward a simplified example. Say Row C has string values for department names at a university (eg "ANTHRO" is Anthropology, "ART-HIST" is Art History, and so on). The university has many departments and the spreadsheet has many entries for each department, but I only want data for Anthropology and Art History. Therefore my task is to write a script that deletes each row that does not satisfy the condition RowC = "ANTHRO" or "ART-HIST".
Problem is, I don't know how in javascript/google-apps-script to define a variable that takes a range of (string) values. One attempt saw me define a "cull" variable as an array containing the conditions the script will judge the data on:
var rowsDeleted = 0;
var keep = ["ANTHRO", "ART-HIST"];
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[2] != keep) {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
Yet it did not work. I know I could simply write if(row[2] != "ANTHRO" || != "ART"), but in reality there are much more than two conditions. Defining the so-called "cull" variable seems more efficient.
Any insights as to why the array-approach did not work? Thank you.
You could try using indexOf. If the row value isn't inside the array, it will return a value of -1, otherwise will return the index.
var rowsDeleted = 0;
var keep = ["ANTHRO",
"ART-HIST"];
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (keep.indexOf(row[2]) === -1) {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
To me, what you're trying to do really is to filter a collecion of data based on speicific conditions. First you can store thousands rows of data into an array, and each element of the array can be a object to represent a row of many columns. And then you can filter the array by condition that whether a row contains string "ANTHRO" or "ART-HIST". A possible code implementaiton is:
var data = [
{ DEPT_ID: 1, ABBR_NAME: "ANTHRO", FULL_NAME: "Anthropology", StudentsNO: 240 },
{ DEPT_ID: 2, ABBR_NAME: "ART-HIST", NAME: "Art History", StudentsNO: 200 },
{ DEPT_ID: 3, ABBR_NAME: "MATH", FULL_NAME: "Mathematics", StudentsNO: 50 },
{ DEPT_ID: 4, ABBR_NAME: "CS", NAME: "Computer Science", StudentsNO: 79 }
];
function isAnthroOrArtHist(element) {
return ["ANTHRO", "ART-HIST"].indexOf(element.ABBR_NAME) >= 0;
}
var newData = data.filter(isAnthroOrArtHist);
I sort of do the same thing where I pull a list of Liquor Inventory to another tab along with a VENDOR name and if the VENDOR name isn't listed, then it hides the row
Here's what I used. You might be able to tweak it to where you need.
function liquorOrderGuideWorking() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('LIQUOR ORDER');
// Rows start at "1" - this will delete the first two rows
//sheet.deleteRows(2, 567);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var liquorInventory = ss.getSheetByName('LIQUOR INVENTORY');
var liquorInventoryRange = liquorInventory.getRange('B6:C573'); //Holds Vendor & Item Name
var liquorInventoryTotalRange = liquorInventory.getRange('I6:I573'); //Holds QTY of each item
var liquorOrder = ss.getSheetByName('LIQUOR ORDER'); //Gets the new sheet
var liquorOrderRange = liquorOrder.getRange('A2:B569'); //Places Vendor & Item Name
var liquorOrderQTYRange = liquorOrder.getRange('C2:C569'); //Places QTY
liquorInventoryRange.copyTo(liquorOrderRange, {contentsOnly:true})
liquorInventoryTotalRange.copyTo(liquorOrderQTYRange, {contentsOnly:true})
var s = ss.getSheetByName('LIQUOR ORDER');
var lastCol = s.getLastColumn();
var lastRow = s.getLastRow();
// assumes headers in row 1
var r = s.getRange(2, 1, lastRow - 1, lastCol);
// Note the use of an array
r.sort([{ column: 1, ascending: true }, { column: 2, ascending: true}]);
var ssHide = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ssHide.getSheetByName('LIQUOR ORDER');
var maxRows = sheet.getMaxRows();
//show all the rows
sheet.showRows(1, maxRows);
//get data from column B
var data = sheet.getRange('A:A').getValues();
//iterate over all rows
for(var i=0; i< data.length; i++){
//compare first character, if blank, then hide row
if(data[i][0].charAt(0) == ''){
sheet.hideRows(i+1);
}
}
Browser.msgBox('Liquor Order Guide','The Liquor Order Guide has been refreshed successfully.', Browser.Buttons.OK);
}
Related
I'm using Google Scripts with the aim of calling an already filled cell using the ID. Once the data has been called, the user would be able to update and send it back to the main "data", in case some update is necessary.
The code isn't giving any error, but is not updating the cells either. See below an example of the code:
function Update() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS = ss.getSheetByName("App"); // From sheet
var dataS = ss.getSheetByName("Results"); // Get the data already inputted
var str = formS.getRange("D6").getValue(); // The ID to find and to update in the "Inputs" sheet
var values = dataS.getDataRange().getValues();
for (var i = 0; i<=values.length; i++) {
var rowFinder = values[i];
if (rowFinder[columnIndex] == str) {
var rowNumber = i+1;
var updatedValues = [[formS.getRange("D9").getValue(),
formS.getRange("D13").getValue(),
formS.getRange("D14").getValue(),
formS.getRange("D15").getValue(),
formS.getRange("D16").getValue(),
formS.getRange("D17").getValue(),
formS.getRange("D18").getValue(),
formS.getRange("D19").getValue()]];
dataS.getRange(rowNumber, 2, 1, 17).setValues(updatedValues);
}
break;
}
}
Find also an example of the sheet that I'm using, where you could also see the whole code (everything else is working fine). Spreadhsheet here
Why function is not updating the data? I've tried to put the dataS.getRange(rowNumber, 2, 1, 17).setValues(updatedValues); in different parts of the code and it doesn't work either. Any ideas?
It is best to get the values of a range all at once and iterate through the entire range than pulling individual cells.
I tested this on your sample spreadsheet (Thanks for including!)
function SubmitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS = ss.getSheetByName("App"); // Add the name of the sheet where the data is going to be retrieved
var dataS = ss.getSheetByName("Results"); // Name of the sheet where the data should appear
//get the entire form range
var values = formS.getRange(6, 3, 14, 2).getValues();
//Create an array to paste into the Results tab
var pasteArr =[];
//iterate through the two columns on the App tab, only add values to the paste array
//if Col C is not blank and does not equal "Choose Params"
for (var i in values) {
//the values array looks like this [[row1Col1, row1Col2],[row2Col1, row2Col2]..]
//so to get to the value in row i, column 1 you use values[i][0]
let field = values[i][0];
if (field != "" && field != "Choose Params") {
let val = values[i][1];
pasteArr.push(val);
}
};
Logger.log(pasteArr);
var dataLastRow = dataS.getLastRow() + 1;
//Build the range to paste in - use the length of pasteArr to get the proper number of columns
//then to make pasteArr a 2D array put pasteArr inside another array by using square brackets
dataS.getRange(dataLastRow, 1, 1, pasteArr.length).setValues([pasteArr]);
Logger.log("Done");
ClearCell();
}
Your other functions have similar issues.
function Update() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS = ss.getSheetByName("App"); // From sheet
var dataS = ss.getSheetByName("Results"); // Get the data already inputted
var dataVals = dataS.getDataRange().getValues();
//get the entire form range
var values = formS.getRange(6, 3, 14, 2).getValues();
//Create an array to paste into the Results tab
var pasteArr =[];
//iterate through the two columns on the App tab, only add values to the paste array
//if Col C is not blank and does not equal "Choose Params"
for (var i in values) {
let field = values[i][0];
if (field != "" && field != "Choose Params") {
let val = values[i][1];
pasteArr.push(val);
}
}
let numCols = pasteArr.length;
//iterate through the rows on the data tab, update the row with the matching ID
for (var j in dataVals) {
let dataTableId = dataVals[j][0];
let formID = pasteArr[0];
Logger.log(dataTableId + " , "+formID);
if (dataTableId === formID){
//have to convert j from 0 index to columns by adding 1. The + before the j and the 1 force app script to treat it as a number.
dataS.getRange(+j+ +1, 1, 1, numCols).setValues([pasteArr]);
Logger.log("dataTableId matches");
}
}
Logger.log("Done");
}
I've been jogging my brain trying to figure out how to write this script to transpose data from one sheet to another from a pretty dirty sheet.
There are other questions like this but none seem to be like my particular use case.
This is how the sheet is currently structured (somewhat):
The biggest issue here is I have no concrete idea how many rows a particular group of data will be, But I know there are always a bunch of blank rows between each group of data.
I found a script that took me half way:
function myFunction() {
//Get values of all nonEmpty cells
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange("D:D").getValues().filter(String);
//Create object with 3 columns max
var pasteValues = [];
var row = ["","",""];
for (i = 1; i<values.length+1; i++){
row.splice((i%3)-1,1,values[i-1]);
if(i%3 == 0){
pasteValues.push(row);
var row = ["","",""]
}
}
if(row != []){
pasteValues.push(row)
}
//Paste the object in columns A to C
ss.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
But in that case the asker dataset was fixed. I can loosely say that the max number of rows each group would have is 10(this is an assumption after browsing 3000 rows of the sheet...but if the script can know this automatically then it would be more dynamic). So with that in mind...and after butchering the script...I came up with this...which in no way works how it should currently(not all the data is being copied):
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues().filter(Array);
var pasteValues = [];
var row = [];
for (i = 1; i<values.length; i++){
if(values[i] != ""){
row.push(values[i])
}
Logger.log(row);
if(i%10 == 0){
pasteValues.push(row);
row = []
}
}
if(row != []){
pasteValues.push(row)
}
copyto.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
I'm pretty sure I should maybe still be using array.splice() but haven't been successful trying to implement it achieve what i want, here's how the transposed sheet should look:
Info:
Each group of addresses inside the "copyfrom" sheet would be separated by at least 1 blank line
The length of an address group is not static, some can have 5 rows, others can have 8, but address groups are always separated by blank rows
Any help is appreciated
You are right to iterate all input values, and I can suggest the similar code:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues();
var pasteValues = [[]]; // keep result data here
values.forEach(function(v) { // Iterate all input values
// The last row to be filled in currently
var row = pasteValues[pasteValues.length - 1];
if (v[0]) {
row.push(v[0]);
} else if (row.length > 0) {
while (row.length < 10) {
row.push(''); // Adjust row length
}
pasteValues.push([]);
}
});
if (pasteValues[pasteValues.length - 1].length == 0) pasteValues.pop();
copyto.getRange(1, 1, pasteValues.length, pasteValues[0].length).setValues(pasteValues);
}
Solution:
Assuming that every new row begins with Name, you can use this script to rearrange the column:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyFrom');
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyTo');
var lastRow = copyfrom.getLastRow();
var values = copyfrom.getRange(1,1,lastRow).getValues().filter(Array);
var pasteValues = [];
var row = [];
var maxLen = 1;
// rearrange rows
for (i = 0; i < values.length; i++) {
if (values[i] == "Name" && i > 0) {
pasteValues.push(row);
row = [values[i]];
}
else if (values[i] != "") {
row.push(values[i]);
if (row.length > maxLen) {
maxLen = row.length;
}
}
}
pasteValues.push(row);
// append spaces to make the row lengths the same
for (j = 0; j < pasteValues.length; j++) {
while (pasteValues[j].length < maxLen) {
pasteValues[j].push('');
}
}
copyto.getRange(1,1,pasteValues.length,maxLen).setValues(pasteValues);
}
Sample I/O:
As far as I can tell, there is no way to get the columns to line up in the output since you don't have any way to tell the difference between, for example, an "address 2" and a "City".
However, as far as merely grouping and transposing each address. This one formula, in one cell, in the tab here called MK.Help will work from the data you provided. It will work for as many contacts as you have.
=ARRAYFORMULA(QUERY(QUERY({A:A,IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),COUNTIFS(IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),A:A,"<>",ROW(A:A),"<="&ROW(A:A))},"select MAX(Col1) where Col1<>'' group by Col2 pivot Col3",0),"offset 1",0))
I have a list of 290 items with 4 columns, which I need to duplicate. I have in a Spreadsheet Row some departements and under it a lot of systems. for each system I need to duplicated the 290 values and add one index column at the front, the department in column 6 and the system in column 7.
I am using the following code:
const ssOrg = SS.getSheetByName("OrgStructure");
function myFunction() {
var afinal = [];
var aDevs = ssDeliverables.getDataRange().getValues();
aDevs.shift();
var lastRow = ssOrg.getLastRow();
var lastColum = ssOrg.getLastColumn();
var count = 1
for (var spalte = 1; spalte <lastColum; spalte++){
var squad = ssOrg.getRange(3,spalte).getValue();
for (var reihe=5; reihe <lastRow; reihe++) {
var system = ssOrg.getRange(reihe,spalte).getValue();
if (system !== ""){
aDevs.map(function(row){
row[0] = count;
row[5] = squad;
row[6] = system;
count ++
return row
})
Logger.log(system);
afinal = afinal.concat(aDevs);
}
}
}
var lastDataRow = ssAssessmentLogic.getLastRow();
ssAssessmentLogic.getRange(2,1,lastDataRow-1,10).clearContent();
var rngResult = ssAssessmentLogic.getRange(2,1,afinal.length,7);
rngResult.setValues(afinal);
}
The problem is that the array at the end (16000 rows) has the same value for each row in column 6 and 7. It is allways the last system & department combination that appears in all 16000 rows.
Where am I wrong?
The question was a little confusing for me but I followed your specifics in the comments section where you explain what exactly info to copy and how and where to paste it.
This gets the job done:
const ss = SpreadsheetApp.getActive();
// this gets the "deliverables" and the "departments", this last ones in a list
// and for each department runs the function to add the complete new modified array to the spreadsheet
function fillsNewSheet(){
var newSheet = ss.getSheetByName('List_DeliverablesALL');
// hardcoded the titles
newSheet.getRange(1, 1, 1, 6).setValues([['taskHasDeliverableNumber', 'Deliverable Description', 'SDP Task', 'SDP Milestone', 'Group', 'Department']])
var deliverables = getDeliverables();
var departments = getDepartments();
for(i=0;i<departments.length;i++){
var departmentsGroup = departments[i];
for(j=0;j<departmentsGroup.length;j++){
addsNewSection(deliverables, departmentsGroup[j])
}
}
}
// this just gets de array of values we are gonna paste for each department in the structure sheet.
function getDeliverables(){
var deliSheet =ss.getSheetByName('Deliverables1');
var deliValues = deliSheet.getRange(2, 2, deliSheet.getLastRow()-1, 4).getValues();
return deliValues;
}
// As the departments are in different columns with different row counts,
// I go over the whole columns and rows and create a single list with all "department" and "group" pairs
function getDepartments(){
var structureSheet = ss.getSheetByName('OrgStructure');
var cols = structureSheet.getLastColumn();
var groups = structureSheet.getRange(3, 1, 1, cols).getValues()[0]
var departments = [];
for(i=1;i<=cols;i++){
var group = groups[i-1];
var groupDeps = structureSheet.getRange(5, i, structureSheet.getLastRow(), 1).getValues();
var subDeps = []
for(j=0;j<groupDeps.length;j++){
subDeps.push([group, groupDeps[j][0]])
}
var filtered = subDeps.filter( function (data) { return data[1] != "" });
departments.push(filtered);
}
return departments;
}
// finally this gets the complete list of "deliverables" from the first sheet, and one specific department.
function addsNewSection(deliverables, department){
var newSheet = ss.getSheetByName('List_DeliverablesALL');
// and in every row of the deliverables list we add the corresponding department/group pair to get the new modified array.
var newSection = []
for(k=0;k<deliverables.length;k++){
var newRow = deliverables[k].concat(department)
newSection.push(newRow)
}
// when this is complete I paste the whole new array in the third sheet.
newSheet.getRange(newSheet.getLastRow()+1, 1, newSection.length, 6).setValues(newSection)
}
I want to remove duplicates across 2 different sheets.
I have my active sheet, and I want to remove duplicates that already exist in my sheet "Blacklist". I want to run this process for both Column A and Column B (or simply for any values across the entire sheets). When a duplicate is found, I want to leave the row in tact but replace the value with '' (e.g. an empty cell).
I have a working version I mangled together, but only for the active sheet.
N.B. it's the findDuplicate function that I use, the removeDuplicate function I left there not to mess anything up :)
// this is a Google Apps Script project
function onOpen() {
var spreadsheet = SpreadsheetApp.getActive();
var menuItems = [
{ name: 'Find duplicates...', functionName: 'findDuplicate' },
{ name: 'Remove duplicates...', functionName: 'removeDuplicate' }
];
spreadsheet.addMenu('Duplicates', menuItems);
}
function removeDuplicate() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveRange();
var data = range.getValues();
var rowNum = range.getRow();
var columnNum = range.getColumn();
var columnLength = data[0].length;
var uniqueData = [];
var duplicateData = [];
// iterate through each 'row' of the selected range
// x is
// y is
var x = 0;
var y = data.length;
// when row is
while (x < y) {
var row = data[x];
var duplicate = false;
// iterate through the uniqueData array to see if 'row' already exists
for (var j = 0; j < uniqueData.length; j++) {
if (row.join() == uniqueData[j].join()) {
// if there is a duplicate, delete the 'row' from the sheet and add it to the duplicateData array
duplicate = true;
var duplicateRange = sheet.getRange(
rowNum + x,
columnNum,
1,
columnLength
);
duplicateRange.deleteCells(SpreadsheetApp.Dimension.ROWS);
duplicateData.push(row);
// rows shift up by one when duplicate is deleted
// in effect, it skips a line
// so we need to decrement x to stay in the same line
x--;
y--;
range = sheet.getActiveRange();
data = range.getValues();
// return;
}
}
// if there are no duplicates, add 'row' to the uniqueData array
if (!duplicate) {
uniqueData.push(row);
}
x++;
}
}
function findDuplicate() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveRange();
var data = range.getValues();
var rowNum = range.getRow();
var columnNum = range.getColumn();
var columnLength = data[0].length;
var uniqueData = [];
// iterate through each 'row' of the selected range
for (var i = 0; i < data.length; i++) {
var row = data[i];
var duplicate = false;
// iterate through the uniqueData array to see if 'row' already exists
for (var j = 0; j < uniqueData.length; j++) {
if (row.join() == uniqueData[j].join()) {
// if there is a duplicate, highlight the 'row' from the sheet
duplicate = true;
var duplicateRange = sheet.getRange(
rowNum + i,
columnNum,
1,
columnLength
);
duplicateRange.setValue('');
}
}
// if there are no duplicates, add 'row' to the uniqueData array
if (!duplicate) {
uniqueData.push(row);
}
}
}
Thanks so much for your help! I've been at this for a few hours and figured I should just ask the experts for advice :)
The first lines of both your removeDuplicate and findDuplicate function seems indeed to indicate that you refer to the active spreadsheet / sheet / range
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveRange();
var data = range.getValues();
If you want to be able to use the same function for a given spreadsheet / sheet / range which is not the active one, you will need to use other functions than the getActiveXXX().
For example, to get the sheet named "Blacklist", you should use
sheet = spreadsheet.getSheetByName("Blacklist")
(see also https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getsheetbynamename)
If you want to access a specific range which differs from the active range, you should use the getRange method (see also https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getrangea1notation)
Note that getRange method can be used in different ways, e.g.
getRange("A1:D4"), getRange(1, 1, 3, 3) (the parameters being respectively startRow, startColumn, numRows,numColumns)
Additionally, if you don't want to hardcode the last line of your 2 columns, you will most probably need this function to find the last line in the code :
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getlastrow
(there is also an example there showing how to use getRange() in combination with getLastRow()).
I hope this will help you going further.
Please note that I didn't check the rest of your code and just assumed that your deduplication logic works fine as you mentioned it in your commment.
Good luck !
I currently have a list with two columns. The first column is student name, and the second column is the number of points they have.
I imported this list from multiple spreadsheets so there were many duplicates on the names of the students. I am able to remove the duplicates, but I want to keep a tally on the total points they have. For example:
Amy 10
Bob 9
Carol 15
Amy 12
would turn into:
Amy 22
Bob 9
Carol 15
This is what I have so far:
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var data = sheet.getRange("A2:B1000").getValues();
var newData = new Array();
var k = 0
var finallist = []
for(i in data){
k++;
var row = data[i];
var duplicate = false;
for(j in newData){
if(row[0] == newData[j][0]){
duplicate = true;
var storedHour = sheet.getRange("B"+k).getValue();
var position = finallist.indexOf(row[0]);
var originalCell = sheet.getRange("B"+(position+1));
var originalHour = originalCell.getValue();
originalCell.setValue(originalHour + storedHour);
sheet.getRange(k,2).setValue("")
sheet.getRange(k,1).setValue("")
}
}
if(!duplicate){
newData.push(row);
finallist.push(row[0])
}
}
}
The problem I'm having is that we have a really large data sample and I'm afraid it may run over Google's 5 minute maximum execution time. Is there another more efficient way to achieve my goal?
Your code is running slow because Spreadsheets API methods (like getRange) are time consuming and much slower then other JavaScript code.
Here is optimized function with reduced number of such Spreadsheets API calls:
function calcNumbers()
{
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var lastRow = sheet.getLastRow();
var dataRange = sheet.getRange(2, 1, lastRow-1, 2);
var data = dataRange.getValues();
var pointsByName = {};
for (var i = 0; i < data.length; i++)
{
var row = data[i];
var curName = row[0];
var curNumber = row[1];
// empty name
if (!curName.trim())
{
continue;
}
// if name found first time, save it to object
if (!pointsByName[curName])
{
pointsByName[curName] = Number(curNumber);
}
// if duplicate, sum numbers
else
{
pointsByName[curName] += curNumber;
}
}
// prepare data for output
var outputData = Object.keys(pointsByName).map(function(name){
return [name, pointsByName[name]];
});
// clear old data
dataRange.clearContent();
// write calculated data
var newDataRange = sheet.getRange(2, 1, outputData.length, 2);
newDataRange.setValues(outputData);
}
Sorting before comparing allows looking at the next item only instead of all items for each iteration. A spillover benefit is finallist result is alphabatized. Execution time reduction significant.
function sumDups() {
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var data = sheet.getRange("A2:B" + sheet.getLastRow()).getValues().sort();
var finallist = [];
for(var i = 0; i<= data.length - 1; i++){
var hours = data[i][1];
while((i < data.length - 1) && (data[i][0] == data[i+1][0])) {
hours += data[i+1][1];
i++;
};
finallist.push([data[i][0], hours]);
};
Logger.log(finallist);
}
Edit: the simple data structure with the name being in the first column allows this to work. For anything more complex understanding and applying the methods shown in #Kos's answer is preferable