Apps Script - compare two arrays, find differences (value and index of cells) - arrays

I have two vertical 1D arrays, one with existing data, and another that could (potentially) have differences.
I want to be able to compare the two arrays, find the differences AND the index of each cell that is different.
Then, I would use that information to find the appropriate cells in the existing data that need to be changed, and set the new values in those specific cells, without having to copy and paste the entire array.
For example:
Existing Data
New Data
John Smith
John Smith
012345
012345
6th grade
7th grade
555-1234
555-1357
Trumpet
Trumpet
5th period
2nd period
Jane Smith
Jane Smith
js#email.com
js#email.com
In this case, the code would see that rows 3,4, and 6 have differences, save those new values and their places in the array, then update the appropriate values in the main data list without changing anything else.
I've tried multiple ways to compare the two arrays AND get the index of the rows that have differences and this is as far as I've gotten without an error or a 'null' result:
function updateInfo() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var currentSheet = ss.getSheetByName("CURRENT");
var infoSheet = ss.getSheetByName("INFO Search");
var origVal = infoSheet.getRange(5,2,52,1).getValues();
var newVal = infoSheet.getRange(5,4,52,1).getValues();
var list = [];
var origData = origVal.map(function(row,index){
return [row[0],index];
});
Logger.log(origData);
var newData = newVal.map(function(row,index){
return [row[0],index];
});
Logger.log(newData);
This just gives me the value and index of each cell.
Is there a fast and efficient way to compare the two arrays, get the data I need, and change the values in just certain cells of the original column?
I can't just copy and paste the whole column over because there are formulas embedded in various rows that need to remain intact.
Thanks for any help you can provide!

Solution:
Iterate through the new data array. For each value, check whether the new value matches the old one. If that's the case, add the corresponding value and index to your list.
Then iterate through your list, and for each pair of value and index, write the value to the corresponding cell.
Code snippet:
newVal.forEach(function(row, index) {
if (row[0] === origVal[index][0]) {
list.push([row[0], index]);
}
});
var firstRow = 2; // Change according to your preferences
var columnIndex = 1; // Change according to your preferences
list.forEach(data => {
sheet.getRange(data[1] + firstRow, columnIndex).setValue(data[0]);
});
Notes:
I don't know where should the data be written, so I'm using undefined sheet, and also random values for firstRow and columnIndex. Please change these according to your preferences.
It would be much more efficient, from a script perspective, to write the whole column to your destination range at once, since that would minimize the number of interactions between the script and the spreadsheet (see Use batch operations). Since you want to write only the updated data, though, I provided a script that does this.

Related

Matching similar (momentJs) dates between two arrays of (momentJs) dates into a new array

In Short: I'm looking for all the dates in firstDateList that have a matching date in secondDateList within some threshold (which can differ from seconds to minutes) (Thanks for #gloo's comment).
I'm struggling to figure out what seems like some basic stuff with
two lists of dates (im using momentjs). (This is for displaying specific points on a highcharts graph).
I have to go through each of the first dates, and find matching dates with the second list of dates. The issue is, I can get dates that are similar to each other, so they can be off by a bit (previously just needed to get exact (isSame) dates, but that wont work now as some dates are off by a few seconds to minutes).
I'm checking the diff between each, but I don't fully understand to only get the dates that I need.
Right now its returning every point up to the first date in the "secondDateList" (the naming is confusing, sorry)
any ideas as to get only the matching (closest to each) dates within both lists?
Am I right for using forEach for both arrays? I dont fully know how to only push the similar dates into a new array.
I think I should filter in the first array so I return the new array of dates that match what I want? I'm not too sure anymore..
const closestDatePoints: GraphPoint[] = [];
let closestPointDifference: number | null = null;
firstDateList?.forEach((firstDate, index) => {
const formattedFirstDate = moment(firstDate[0]); // this just gets the date from this firstDate object
secondDateList?.forEach((secondDate, index) => {
// const isSame = date.isSame(formattedFirstDate);
const differenceInMinutes = Math.abs(
moment(secondDate)?.diff(formattedFirstDate, 'minutes')
);
if (
closestPointDifference === null ||
closestPointDifference > differenceInMinutes
) {
closestPointDifference = differenceInMinutes;
// I realize that this is pushing dates that I also do not
// want - its pushing all dates until the first, firstDate, and
// stopping once it hits that first firstDate. Don't know how
// to make it return ***only*** the dates I need.
closestDatePoints.push(firstDate);
}
});
});
This problem is like finding the intersection of two arrays, except the values can be within a threshold instead of being strictly equal. Here is a naive approach that is pretty short and simple:
const closestDates = firstDateList.filter((date1) =>
secondDateList.some((date2) => Math.abs(date1.diff(date2)) < threshold)
);
I have not been able to test this on real dates yet and this is far from the fastest approach, so I'm not sure if it will work for your case, but for small arrays this is pretty readable.
If you need faster performance you would need to first ensure both arrays are sorted to prevent some redundant comparisons

Moving rows from spreadsheets to new documents based on owner name with filling information in document

I have a spreadsheet named Project log where I have 6 columns,
Timestamp Task Owner Date Keywords Email
Here we have different owners. I need to create a document with the owner name and move particular owner row to that owner document.
I am able to do this by looping but I had a issue when I get duplicate names it is creating another document and sending data but as per my requirement I need to have only one document on that particular owner and move duplicate owner name to owner itself.
Example of owner column.
joe
john
john
Here I need to create a document with "joe" name and append body with that whole row but I have two "john"s I need to create only one document i.e., "john" and move two rows to single document if I find more on his name I should be able to send that row to that particular owner document.
And my looping code is:
function createDocFromSheet() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.setActiveSheet(ss.getSheets()[2]);
var numRows=ss.getLastRow();
var values = ss.getDataRange().getValues()
for(n=2;n<=values.length;++n) {
var cell = sheet.getRange(n,3).getValue();
var row = sheet.getRange(n,2,1,5).getValues();
var newDoc = DocumentApp.create("Task Report - "+cell);
var body = newDoc.getBody();
body.insertParagraph(0,row);
newDoc.saveAndClose();
}
}
If the name is repeating in the column for just once, you can get the firstIndex and lastIndex of the range array and check if those two positions matches or not.
Giving an algorithmic code below:
var array1 = sheet.getRange(1,3,sheets.getLastRow()).getValues();
(Example values might be like array1 = [a,b,c,a,c,d])
var var1 = sheet.getRange(n,3).getValue();
var var2 = array1.indexOf(var1);
var var3 = array1.lastIndexOf(var1);
if(var2 == var3) {
// this means there is no repetition of the name.
}
else{
//get the values for both rows and create a document.
// Make a flag variable array to note that a doc is created with this name.
}
Hope that helps!

D3.js unknown number of columns and rows

I m currently creating a chart(data from an external csv file) but I dont know beforehand the number of columns and rows. Could you maybe point me in the right direction as to where I could find some help(or some examples) with this issue?
Thank you
d3.csv can help you here:
d3.csv('myCSVFile.csv', function(data){
//the 'data' argument will be an array of objects, one object for each row so...
var numberOfRows = data.length, // we can easily get the number of rows (excluding the title row)
columns = Object.keys( data[0] ), // then taking the first row object and getting an array of the keys
numberOfCOlumns = columns.length; // allows us to get the number of columns
});
Note that this method assumes that the first row (and only the first row) of your spreadsheet is column titles.
In addition to Tom P's advice, it's worth noting that version 4 of D3 introduced a columns property, which you can use to create an array of column headers (i.e. the dataset's 'keys').
This is useful because (a) it's simpler code and (b) the headers in the array are in the same order that they appear in the dataset.
So, for the above dataset:
headers = data.columns
... creates the same array as:
headers = Object.keys(data[0])
... but the array of column names is in a predictable order.

Array elements disappearing / not loading

I have no idea what is going on here and it's a little bizzare.
I'm adapting a VBA macro into a VB.net project, and I'm experiencing what I would describe as some extreemly unusual behavior of a method I'm using to pass data around in VB.net. Here's the set up...
I have, for indexing reasons, a collection that consists of all open orders:
Public allOpenOrders As New Collection
Within this collection, I store other collections, indexed by account number, that each contain information about each open order in an array that is three elements long. Here is how I'm populating it:
openOrderData(0) = some information
openOrderData(1) = some information
openOrderData(2) = some information
SyncLock allOpenOrders
If allOpenOrders.Contains(accountNumber) Then
'Already in the collection...
accountOpenOrders = allOpenOrders(accountNumber)
accountOpenOrders.Add(openOrderData)
Else
'Not already in collection
accountOpenOrders = New Collection
accountOpenOrders.Add(openOrderData)
allOpenOrders.Add(accountOpenOrders, AccountNumber)
End If
End SyncLock
Here's the thing, if I place a stop after end synclock and check the collection, I can clearly see that the array with all data is there, plain as day. However, when I move on in my code (this is occuring in another thread after the preceeding code has executed) to retrieve it and write it to a workbook...
If allOpenOrders.Contains(accountNumber) Then
accountOpenOrders = allOpenOrders(accountNumber)
For each openOrderArray In accountOpenOrders
OutputSheet.Cells(1, 1).value = accountNumber
For counter = 0 to 2
OutputSheet.Cells(1, counter + 2).value = openOrderArray(counter)
Next counter
Next openOrderArray
End If
I get the first element of the array in column B, but C and D are blank. Even more puzzling, if I put a stop right after the allOpenOrders.Contains line I can look at the collection and the last two elements of the array are now blank. Most puzzling of all, they aren't just blank, they are blanks, a number of blanks equal in length to the original field I recorded in that element of the array?!
Any ideas are appreciated. I can tell you I'm using the same type of method to load other data in this workbook with no problems. These are also the only instances in which the allOpenOrders collection is touched... I'm so confused by these results.

Creating a loop that modifies row after row in google Scripts

rookie coder here. I'm working in Google Scripts for a project and here's what I'm trying to do.
Essentially, I am trying to access a specific spreadsheet (called "Top Sheet") through an AICP address that has been entered in cell K2 of my "2013" spreadsheet. The Information that I am accessing in the "Top Sheet" spreadsheet is a single number located in cell K46. Then, I am taking the value of cell K46 (located in "Top Sheet") and placing it into a new cell (N2) in the "2013" spreadsheet.
Here's what that looks like (and it works).
function GetJobActuals() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s2 =SpreadsheetApp.openById(ss.getSheetByName("2013").getRange("K2:K2").getValue());
var v2 = s2.getSheetByName("Top Sheet").getRange("K46:K46").getValue();
ss.getSheetByName("2013").getRange("N2:N2").setValue(v2);
var s3 = SpreadsheetApp.openById(ss.getSheetByName("2013").getRange("K3:K3").getValue());
var v3 = s3.getSheetByName("Top Sheet").getRange("K46:K46").getValue();
ss.getSheetByName("2013").getRange("N3:N3").setValue(v3);
What I'm trying to do now, it make this happen for all of the preceding cells in the "2013" spreadsheet. For example, this code works for one number. Cell K2 in "2013" tells it to retrieve the value of cell K46 in "Top Sheet" and then returns with that value and places it into a new cell, N2.
How would I loop this so that this process would happen for the cell range of K2 - K60 and the repopulate the new cell range of N2 - N60?
Man I hope this makes sense, any and all help is appreciated. And again, the above code works, just trying to make a loop that can handle more than one thing at a time.
The answer should become clear to you if you use Sheet.getRange(row,column) instead of Sheet.getRange(a1Notation). By using row and column numbers instead of A1 notation, you can apply a for loop to acheive your goal.
ss.getSheetByName("2013").getRange("N3:N3").setValue(v3);
becomes
ss.getSheetByName("2013").getRange(3,14).setValue(v3);
^^^^
Row = 3, Column = 14 ("N")
So you can then iterate over rows:
for ( row=2; row<=60; row++) {
...
var s3 = SpreadsheetApp.openById(ss.getSheetByName("2013").getRange(row,11).getValue());
...
ss.getSheetByName("2013").getRange(row,14).setValue(v3);
...
}
Details left to you, but that's the basic structure for your loop. There are more efficient ways to make this work, but since you're new to this, worry about that after you get the basics working.

Resources