Looping over two columns and copy rows only when items are matching - javascript

I have two sheets with the following datasets :
Sheet 1:
05/12/2016 366505 3299193 217374
06/12/2016 345886 3328374 219832
07/12/2016 328152 3348070 221501
08/12/2016 171627 3308919 222948
09/12/2016 338694 3344380 225481
Sheet 2:
05/12/2016 366505 3299193 217374
06/12/2016 345886 3328374 219832
07/12/2016 328152 3348070 221501
08/12/2016 blank blank blank
09/12/2016 blank blank blank
I would like to be able to loop through each column of the sheet 1
then compare with each column of the sheet 2 and for each date missing, I would like to copy the corresponding data in Sheet 2.
I've been able to wrote this until now, but I'm not sure of the logic I should use and how I should organise my code :
function myFunction() {
var sheet1 = SpreadsheetApp.openById("ID").getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.openById("ID").getSheetByName("Sheet2");
var date1 = sheet1.getRange(2, 1, sheet1.getLastRow()).getValues();
var date2 = sheet2.getRange(2, 1, sheet1.getLastRow()).getValues();
var lastRow = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getLastRow()
for (var i = 0; i < date1.length; i++){
var list1 = date1[i][0];
var list2 = date2[i][0];
if ( //comparison between list1 and list 2 here ){
var data1 = sheet1.getRange(1,2).getValue();
var data2 = sheet1.getRange(1,3).getValue();
var data3 = sheet1.getRange(1,4).getValue();
sheet2.getRange(i+1, 1,sheet1.getLastRow(), sheet1.getLastColumn()).appendRow([data1,data2,data3]);}
}
edit #1: I rewrite most of my question. It wasn't clear at all. I hope it's better now !

Modified #SimonBreton Code to reduce overhead operation. Details of modification are commented in the code. In short don't use getValue/s and setValue/s repeatedly, better to get all the data/date at once to an array, modify that array. Once all the modification is done set values using the array once at the end.
function myFunction() {
var sheet1 = SpreadsheetApp.openById("ID").getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.openById("ID").getSheetByName("Sheet2");
//Get all the values as array rather then using getValues repeatedly.
//Note: also gave num of columns as 3, to get values for all three columns/
var date1 = sheet1.getRange(1, 2, sheet1.getLastRow(),3).getValues(); //getRange(row, column, numRows, numColumns)
var date2 = sheet2.getRange(1, 2, sheet1.getLastRow(),3).getValues();
// You can compare and add data to your date2 array
for (var i = 0; i < date1.length; i++){
test = date1[i][0]
if(i < date2.length){ // make sure you dont exceed the length of the second array
test1 = date2[i][0]
if (test != test1 ) {
date2[i] = date1[i] // Note this is pass by reference, so if you modify date1 within this code. date2 will be also be modified.
}
}
else { //if you have more data in date1 use push to add elements to the end
date2.push(date1[i])
}
}
// Write the data only once at the end with the update date2 array.
sheet2.getRange(1,2,date2.length,date2[0].length).setValues(date2)
}

Got a working code here :
function myFunction() {
var sheet1 = SpreadsheetApp.openById("ID").getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.openById("ID").getSheetByName("Sheet2");
var date1 = sheet1.getRange(1, 2, sheet1.getLastRow()).getValues(); //getRange(row, column, numRows, numColumns)
var date2 = sheet2.getRange(1, 2, sheet1.getLastRow()).getValues();
for (var i = 0; i < date1.length; i++){
test = sheet2.getRange(i+1, 2).getValue()
test1 = sheet1.getRange(i+1, 2).getValue()
if (test != test1 ) {
var data1 = sheet1.getRange(i+1,2).getValue();
var data2 = sheet1.getRange(i+1,3).getValues();
var data3 = sheet1.getRange(i+1,4).getValue();
sheet2.getRange(i+1, 2).setValue(data1);
sheet2.getRange(i+1, 3).setValue(data2);
sheet2.getRange(i+1, 4).setValue(data3);
}
}
}

Related

Google Sheets Random Copy and Sort with Scripts

What I'm attempting to do is copy a column over and re-sort it. Problem is, it captures all available cells and uses the same space to re-sort, causing blank spaces. The idea is to create tournament match pairings, with the first column being the Roster itself, and following columns being players they will be matched against.
I'd also like to add a line that verifies a name doesn't appear twice on the same row, reshuffling until the column is all unique along each row
This is the code I have so far. I attempted to filter the data by swapping
range2.setValues(shuffleArray(range.getValues()));
for
range2.setValues(shuffleArray(range.getValues().filter(String)));
but this results in a "Number of data rows is 10 when range is 41" error, not verbatim obviously. I'm trying to collapse the blank spaces that are shown in this Screenshot.
I'm sure I can figure out how to expand it by however many matches I wish to generate.
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var range = sheet.getRange('A31:A')
var range2 = sheet.getRange('C31:C');
range2.clearContents;
range2.setValues(shuffleArray(range.getValues()));
}
function shuffleArray(array) {
var i, j, temp;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i+1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
EDIT::::: Code has been moved to a test sheet hence different name and ranges, ive adjusted the samples when i moved them of course
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet4');
var range = sheet.getRange('A1:A40')
var v = range.getValues().filter(String);
//Match 1
var values = shuffleArray1(v);
while (v.length != [...new Set(values.map(([a]) => a))].length) {
values = shuffleArray1(v);
}
range.offset(0, 1, values.length).setValues(values);
//Match 2
var values2 = shuffleArray2(v);
while (v.length != [...new Set(values2.map(([a]) => a))].length) {
values = shuffleArray2(v);
}
range.offset(0, 2, values.length).setValues(values2);
}
function shuffleArray1(array) {
var i, j, temp;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i+1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function shuffleArray2(array) {
var u, v, temp;
for (u = array.length - 3; u > 0; u--) {
v = Math.floor(Math.random() * (u+2));
temp = array[u];
array[u] = array[v];
array[v] = temp;
}
return array;
}
Modification points:
I think that range2.clearContents might be range2.clearContent().
In your script, by sheet.getRange('A31:A'), all rows in the sheet are retrieved.
When these points are reflected in your script, how about modifying shuffleRange() as follows?
Modified script:
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var lastRow = sheet.getLastRow();
var range = sheet.getRange('A31:A' + lastRow);
var range2 = sheet.getRange('C31:C' + lastRow);
range2.clearContent();
var values = shuffleArray(range.getValues()).filter(String);
range.offset(0, 2, values.length).setValues(values);
}
I'm not sure about the last row of your sheet. So, I proposed the above modification.
Added 1:
From your following new question,
essentially if the row contains a duplicate it has to reshuffle until each row contains a unique name from the original column, to create unique match pairings for tournaments, this will check the whole row, as some tournaments run only 2 matches, some up to 21
In this case, how about the following sample script?
Sample script:
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var lastRow = sheet.getLastRow();
var range = sheet.getRange('A31:A' + lastRow);
var range2 = sheet.getRange('C31:C' + lastRow);
range2.clearContent();
var v = range.getValues().filter(String);
var values = shuffleArray(v);
while (v.length != [...new Set(values.map(([a]) => a))].length) {
values = shuffleArray(v);
}
range.offset(0, 2, values.length).setValues(values);
}
In this case, when the duplicated values are included in values, shuffleArray function is run again.
Added 2:
From your following reply,
Unfortunately it produced duplicate lines almost immediately once i duplicate the functions to create a second set of results
I added a new sample so you can see how im trying to expand it across several columns of results, this will create a set number of matches. I will, when done, swap the counter for a cell check so a user can set the match number, but thats later
Sample script:
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var range = sheet.getRange('A1:A40');
var v = range.getValues().filter(String);
var createValues = v => {
SpreadsheetApp.flush(); // I'm not sure whether this line is required.
var temp = sheet.getRange(1, 1, 40, sheet.getLastColumn()).getValues();
var existValues = temp[0].map((_, c) => temp.map(r => r[c]).join("")).filter(String);
var values;
do {
values = shuffleArray1(v);
while (v.length != [...new Set(values.map(([a]) => a))].length) {
values = shuffleArray1(v);
}
var check = values.map(([a]) => a).join("");
} while (existValues.some(e => e == check));
return values;
}
var values1 = createValues(v);
range.offset(0, 1, values1.length).setValues(values1);
var values2 = createValues(v);
range.offset(0, 2, values2.length).setValues(values2);
}
In this modification, the new column values are created by checking all existing columns.
Adding to Tanaike's suggestion I've joined your two functions in order to be able to re-shuffle. I'm not as well-versed in coding, and probably there's a more-alike version of your code that also enables the re-shuffling. But you can try this:
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var lastRow = sheet.getLastRow()
var range = sheet.getRange('A31:A' + lastRow);
var range2 = sheet.getRange('C31:C' + lastRow);
range2.clearContents;
function shuffleArray() {
var i, j, temp;
var array = range.getValues()
var array2 = range.getValues()
var count= 1;
while (count>0){
count=0
for(i=array.length-1;i>0;i--){
j = Math.floor(Math.random() * (i+1));
temp = array2[i];
array2[i] = array2[j];
array2[j] = temp;
}
for(i=0;i<array.length;i++){
if(array[i].toString() == (array2[i]).toString()){count = count+1}
}}
return array2
}
range2.setValues(shuffleArray())
}
I've make it try tenths of times and never got a duplicate:

Create a google Script for finding and replacing text in the same cell on each page of a spreadsheet

I have 2 variables that I need to update on several hundred pages of a spreadsheet. But I only need it changed in cell B1 for each page and not any other cells. If B1 is Apple I need it to say Red Apple and if B1 is Banana I need it to say Yellow Banana.
function run() {
runReplaceInSheet();
replaceInSheet();
}
function runReplaceInSheet() {
var spreadsheet = SpreadsheetApp.openById("ID");
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for ( var i = 0 ; i<sheets.length ; i++) {
var sheet = sheets[i];
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, 1) // Numbers of rows to process
// Fetch values for each row in the Range
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var values = sheet.getDataRange().getValues();
// Replace Names
replaceInSheet(values, 'Apple', 'Red Apple');
//write the updated values to the sheet, again less call;less overhead
sheet.getDataRange().setValues(values);
}
}
function replaceInSheet(values, to_replace, replace_with) {
//loop over the rows in the array
for (var row in values) {
//use Array.map to execute a replace call on each of the cells in the row.
var replaced_values = values[row].map(function(original_value) {
return original_value.toString().replace(to_replace, replace_with);
});
//replace the original row values with the replaced values
values[row] = replaced_values;
}
}
Here is the updated code I tried to use that keeps timing out.
function run() {
runReplaceInSheet();
replaceInSheet();
}
function runReplaceInSheet() {
var spreadsheet = SpreadsheetApp.openById("ID");
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]);
var startRow = 1; // First row of data to process
var numRows = 1; // number of rows to process
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, 1) // Numbers of rows to process
// Fetch values for each row in the Range
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var value = sheet.getRange('B1').getValue();
// Replace Names
replaceInSheet(values, 'Apple', 'Red Apple');
//write the updated values to the sheet, again less call;less overhead
sheet.getRange('B1').setValue(value);
}
}
function replaceInSheet(values, to_replace, replace_with) {
//loop over the rows in the array
for (var row in values) {
//use Array.map to execute a replace call on each of the cells in the row.
var replaced_values = values[row].map(function(original_value) {
return original_value.toString().replace(to_replace, replace_with);
});
//replace the original row values with the replaced values
values[row] = replaced_values;
}
}
For every sheet in the spreadsheet the code in the question grabs all the values but you only need to replace the value of a single cell.
Also, the replace function do the replace in all the cells.
To grab the value of B1 instead of
var values = sheet.getDataRange().getValues();
use
var value = sheet.getRange('B1').getValue();
then you could use compare the value to see if the replace is necesary, and if so, instead of
sheet.getDataRange().setValues(values);
you might use
sheet.getRange('B1').setValue(value);

How to add a row to a 3D array Array Google App scripts

I have some data with three columns, (PO, SKU, Units). I've sorted this array by SKU. The data looks like the image below
I'd like to add a blank row in between each set of SKUs to make it easier to read, and perhaps add totals in later. For example, after the last AL-STSTCHOPPER record, and before the first CE-12-SLOT-WOOD-WATCH-DARK record, there needs to be a gap. I've tried using a for loop to do this, with no luck. I think there's an easier way, but not sure what it could be. Code I have for this is as follows:
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SheetName");
var orderLog = SpreadsheetApp.openByUrl("URL").getSheetByName("Order Details");
var orderSort = orderLog.getRange(3, 1, orderLog.getLastRow(), 3).getValues().
sort(function(r1,r2){
var a = r1[1];
var b = r2[1];
if(a>b){
return 1;
}else if (a<b){
return -1;}
return 0;
});
Logger.log(orderSort);
You can add blank row with a loop:
function whitespace() {
var ss = SpreadsheetApp.getActive(); // SpreadsheetApp.open...
var sheet = ss.getActiveSheet(); //ss.getSheetByName('sheetName');
var lastRow = sheet.getLastRow();
sheet
.getRange(1, 2, lastRow)
.getValues()
.reverse()
.forEach(function(row, i, all){
if(all.length > (i+1) && row[0] && all[i+1][0] && row[0] != all[i+1][0]) sheet.insertRowBefore(lastRow - i);
});
}
You can do it by reading and writing an array:
function whitespace2() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var arr = [];
sheet
.getRange(1, 1, lastRow, 3)
.getValues()
.forEach(function(row, i, all){
if(all.length > (i+1) && row[1] && all[i+1][1] && row[1] != all[i+1][1]) {
arr.push(row);
arr.push(['','','']);
}
else arr.push(row);
});
sheet
.getRange(1, 1, arr.length, 3)
.setValues(arr);
}

Google Sheets Script - Looping through all sheets, and adding all values of a certain column into array to then count unique values

I want to loop through all sheets (I was 57) and get all the elements from the first column,and add them into an array to then, that I can later access with ALL the ids from ALL the sheets.The problem with my code is that the ids array is not loading correctly. When I print ids.length it equals 0. So I'm guessing something is wrong on my forEach loop where it won't push the values into the array.
function countLinesPerCharacter() {
let app = SpreadsheetApp;
let spreadsheet = SpreadsheetApp.getActive()
let allSheets = spreadsheet.getSheets()
let targetSheet = app.getActiveSpreadsheet().getSheetByName("lines");
let ids = []
let y = 2
//goes thrrough each sheet
allSheets.forEach(function(sheet){
sheet.activate()
//goes through the rows
//row col
let lastRowNumber = spreadsheet.getLastRow();
for(let i = 0; i < lastRowNumber.length; i++) {
let questionID = spreadsheet.getRange(i, 1).getValue();
ids.push(questionID) // IT WON'T LOAD THE questionID into ids ----
y++
}
})
targetSheet.getRange(1, 5).setValue(ids.length); //ids.length = 0
targetSheet.getRange(1, 1).setValue("Done going through each sheet");
}
Issues:
for(let i = 0; i < lastRowNumber.length; i++) {
lastRowNumber is of type Number and doesn't have a .length property.lastRowNumber.length is undefined and as i is not less than undefined, the loop never starts.
spreadsheet.getLastRow()
This only gets the first sheet's last row. Last row of the current sheet should be retrieved.
spreadsheet.getRange(i, 1)
Spreadsheet doesn't have a .getRange( number, number) method. Only sheet class does.
Modified script:
Old script:
function countLinesPerCharacter() {
const spreadsheet = SpreadsheetApp.getActive();
const allSheets = spreadsheet.getSheets();
const targetSheet = spreadsheet.getSheetByName('lines');
const ids = [];
//goes thrrough each sheet
allSheets.forEach(function(sheet) {
//sheet.activate();
//goes through the rows
//row col
const lastRowNumber = sheet.getLastRow();//modified getlastRow from sheet
for (let i = 1; i <= lastRowNumber; i++) {//modified=> length removed; "<"=>"<=";i=0=>i=1
const questionID = sheet.getRange(i, 1).getValue();//getRange from sheet
ids.push(questionID);
}
});
targetSheet.getRange(1, 5).setValue(ids.length); //ids.length = 0
targetSheet.getRange(1, 1).setValue('Done going through each sheet');
}
Optimized script:
function countLinesPerCharacter2() {
const ss = SpreadsheetApp.getActive();
const targetSheet = ss.getSheetByName('lines');
const ids = ss
.getSheets()
.map(sheet => sheet.getRange(1, 1, sheet.getLastRow()).getValues())
.flat(2);
targetSheet.getRange(1, 5).setValue(ids.length);
}
To Read:
Best Practices
Arrays
Try something like this. I simplified some of your expressions. Couple notes:
Don't forget your semicolons. Good use of let throughout.
Remember that allSheets is every sheet, including targetSheet.
function myFunction() {
let allSheets = SpreadsheetApp.getActive().getSheets();
let targetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("lines");
let ids = [];
//goes thrrough each sheet
allSheets.forEach(function(sheet){
sheet.activate();
//goes through the rows
//row col
let rows = sheet.getRange("A:A").getValues(); // handy way to select the whole first column
rows = rows.filter(row => row[0] !== ""); // remove blank values based on the first column (you may want to remove this)
rows.forEach(row => ids.push(row));
});
// console.log(ids); // optional diagnostic
targetSheet.getRange(1, 5).setValue(ids.length);
targetSheet.getRange(1, 1).setValue("Done going through each sheet");
}

How do I compare the date value in this loop to find duplicates

I have a function that checks for duplicates and remove them using the values in a specified column. If I use any other Column apart from Column A with dates, it works fine, but if I use Column A it doesn't work.
I understand that the code is not barely referring to the date value and I've tried getTime() and other suggestions but I'm not getting the desired result. Here's my code below
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var newData = [];
for (var i in data) {
var row = data[i];
var duplicate = false;
for (var j in newData) {
if (row[0] == newData[j][0]) {
duplicate = true;
}
}
if (!duplicate) {
newData.push(row);
}
}
sheet.clearContents();
sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
}
I'll be glad to get help to make this code delete duplicate rows based on duplicates found in Column A
It doesn't work because (and I'm assuming that row[0] is a date object) two date instances of the exact same date resolve to false as can be seen here:
const a = new Date();
const b = new Date(a.getTime());
console.log(a,b);
console.log(a === b);
What you should do instead is to compare the timestamp:
const a = new Date();
const b = new Date(a.getTime());
console.log(a,b);
console.log(a.getTime() === b.getTime());
Thus the change you need to do is the following:
if (row[0].getTime() == newData[j][0].getTime()) {
duplicate = true;
}
You can also simplify your loop with the following (no arrow functions, just replace with a normal function):
function removeDuplicates() {
const sheet = SpreadsheetApp.getActiveSheet();
const data = sheet.getDataRange().getValues();
const newData = data.reduce(function(a,row){
const [currentDate] = row;
const duplicate = a.every(function([[date]]){
return date.getTime() !== currentDate.getTime());
});
if(!duplicate){
a.push(row);
}
return a;
}, []);
sheet.clearContents();
sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
}
Try this:
function removeDuplicateDates() {
var m=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];//used to convert month to index
var headerRows=3;//number of header rows
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet177');//change sheet name
var rg=sh.getDataRange();
var vA=rg.getValues();
var dvA=[];//an array of date values
var d=0;//deleted row counter
for(var i=headerRows;i<vA.length;i++) {
var tA=vA[i][0].toString().split(/,| /);//splits on comma or space using a regular expression
var dv=new Date(tA[3],m.indexOf(tA[1]),tA[2]).valueOf();
if(dvA.indexOf(dv)==-1) {
dvA.push(dv);//if current value is not in dvA then it is unique so put it in dvA
}else{
sh.deleteRow(i+1-d++);//this deletes duplicates based upon a date string in this format Mon,Jul 1,2019
}
}
}
This one keeps the last one that matches I think. It may need some tweaking.
function removeDuplicateDates() {
var m=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];//used to convert month to index
var headerRows=3;//number of header rows
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet177');//change sheet name
var rg=sh.getDataRange();
var vA=rg.getValues();
var dvA=[];//an array of date values
var rA=[];//a row array
var d=0;//deleted row counter
for(var i=headerRows;i<vA.length;i++) {
var tA=vA[i][0].toString().split(/,| /);//splits on comma or space using a regular expression
var dv=new Date(tA[3],m.indexOf(tA[1]),tA[2]).valueOf();
var idx=dvA.indexOf(dv);
if(idx==-1) {
dvA.push(dv);//if current value is not in dvA then it is unique so put it in dvA
rA.push(i+1);
}else{
sh.deleteRow(rA[idx]-d++);//this deletes duplicates based upon a date string in this format Mon,Jul 1,2019
dvA.splice(idx,1);//remove old value
rA.splice(idx,1);
dvA.push(dv);//replace with new value
rA.push(i+1);
}
}
}

Categories

Resources