Going from an array to object and creating subclasses - arrays

I am a beginner in google app script and would like to ask a question regarding how to create subclasses for an object based on an array of information drawn from my spreadsheet.
Here is an example sheet with some data in the sheet "History". The input data is the transactional history of the investment of a user. My end goal is to create an array inside google app script with adjusted stock-split values for any given stock.
However, the first step in my project would be to gather the data in such a manner that I can perform these calculations. For this, I would need to create an object such as this:
stock symbol: {date:value, {quantity: value, price:value}}, {date:value, {split ratio:value}}
The reason for this is because in this object the dates are linked to quantity price and split ratio. In later calculations I would look if the date of the split value is less or equal to the date of the quantity/price value, if this is true then perform split ratio * quantity and price/split ratio. If this is not true, then leave the price and quantity as is, for any given stock. Finally return these object in the same form as the orginal array.
This is the attempt I have made so far:
function createDate(date, quantity, price) {
this.date = date;
this.quantityPrice = new createDateData (quantity, price);
}
function createDateData(quantity, price) {
this.quantity = quantity;
this.price = price;
}
function retrieveData () {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const inputSheet = ss.getSheetByName('History');
const data = inputSheet.getRange(2, 1, inputSheet.getLastRow() - 1, 9).getValues();
const filterd = data.filter(row => row[2] == 'Buy' || row[2] == 'Sell' || row [2] == 'Split');
const sorted = filterd.sort((a, b) => {
if (a[0] < b[0]) return -1
if (a[0] > b[0]) return 1
else return 0
})
for ( let i of sorted) {
var sampleData= new createDate([i][0][0],[i][0][3],[i][0][4]);
console.log(sampleData);
}
}
This is the output I get
{ date: Tue Jun 30 2020 18:00:00 GMT-0400 (Eastern Daylight Time),
quantityPrice: { quantity: 1, price: 40000 } }
Which is different than from the desired output?
Question: How do I get the desired output? For example, in the case of AMZN:
AMZN: {9/28/2020, {1, 100}}, {9/28/2020, {0.5, 200}}, {10/19/2020 {0.2, 100}}, {11/27/2020, {10}}
EDIT2: Please see sheet "Desired Output" for desired output.

As far as I can tell you have the array formula in column 'Price', so you need to update the column 'Quantity' only. The column 'Price' will updated automatically.
And are you sure that the line 27 in your desired output should change? I think it should not, since it's 'Cash', not 'AMZN'.
Try this code:
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('History');
var range = sheet.getRange(3,1,sheet.getLastRow(),9);
var data = range.getValues().reverse(); // reverse to iterate it from the top to the bottom
for (var row in data) {
var action = data[row][2];
if (action != 'Split') continue;
data = [...data.slice(0,row), ...update(data.slice(row))]; // update all rows below the 'Split'
}
// get column 'Quantity' from the data and put it on the sheet
var quantity = data.map(x => [x[3]]).reverse();
sheet.getRange(3,4,data.length,1).setValues(quantity);
}
// the function takes rows, recalculates quantity and returns the updated rows
function update(data) {
var security = data[0][1];
var ratio = data[0][8];
// if the security is the same as in the first row,
// and if the action is 'Buy' or 'Sell':
// the price will be multiply by the ratio
for (var row in data) {
if (data[row][1] != security) continue;
var action = data[row][2];
if (['Buy','Sell'].includes(action)) data[row][3] *= ratio;
try {if (row[+r+1][1] == 'Split') break} catch(e) {} // <-- updated line
}
return data;
}
The script implies that all the rows are sorted by date. Newest dates at the bottom, oldest dates at the top.
There is no any objects. I see no need for them. Probably it could be done another way, with objects.

Related

API doesn't call with a variable. React.js, moment-business-days

I have been stuck on this problem for a while now, so I think it is time to ask my cool worldwide geniuses.
What I am trying to do: Create An array of arrays that pairs a date and data automatically (through a loop). Complicating factors including trying to get dates of only weekdays and exclude holidays.
Problem: When I use a variable, the API data doesn't display. This particular line is the problem arrayOfDateAndPrice.push(data[2].data['Time Series (Daily)'][today]['4. close'])
the 'data' holds the API that I am fetching. I am digging through the JSON in data[2].data['Time Series'] etc. When I insert the date with a variable (i.e date I got from the loop, I checked, it fetches the correct date in string format). it doesn't work. BUT, when I insert a stringed normal date (i.e. '2022-03-04') it displays the data!? What the heck am I missing?
Error: TypeError: undefined is not an object (evaluating 'data[2].data['Time Series (Daily)'][today]['4. close']')
const calculateBusinessDay = (data) => {
const presDay = '02-21-2022';
const goodFri = '04-15-2022';
const memDay = '05-30-2022';
const junTeenth = '06-20-2022';
const july4th = '07-04-2022';
const laborDay = '09-05-2022';
const thanksGiving = '11-24-2022';
const xMas = '12-26-2022';
var today;
var arrayOfDateAndPrice = [];
const arrayOfAllData = [];
const weekdays = momentBusiness
weekdays.updateLocale('us', {
holidays: [mlk, presDay, goodFri, memDay, junTeenth, july4th, laborDay, thanksGiving, xMas],
holidayFormat: 'MM-DD-YYYY',
workingWeekdays: [1, 2, 3, 4, 5]
});
for (let i = 0; i < 30; i++) {
today = weekdays().subtract(i, 'days').format('YYYY-MM-DD')
if (weekdays(today).isBusinessDay() && !weekdays(today).isHoliday() == true) {
arrayOfDateAndPrice.push(today)
arrayOfDateAndPrice.push(data[2].data['Time Series (Daily)'][today]['4. close'])
arrayOfAllData.push(arrayOfDateAndPrice)
arrayOfDateAndPrice = [];
}
}
return (
arrayOfAllData
)
}
I figured it out?!?! It was because my 'i' variable started the day from "today" which was Japan time. No market data for Japan time since we are "ahead" in time lol. Oh gosh. That feels good...
The 'i' had to start at i=1, to subtract one day from today and start the loop.

Google AppScript - How to skip certain columns[INDEXes] when setting values from an ARRAY

I am new to coding.
I am trying to edit and set values from an entire row but want to skip certain columns, because there are formulas in it.
In short: I want to / have to keep track of sing-in and sign-out times, which then will be calculated in the spreadsheet but shouldn't be overwritten by the array. Is there a way to skip every 3rd "value"/index (as these are the columns which have the formulas)?
In fact I want to skip the columns: TOTAL, day1tot, day2tot, day3tot .... day14tot.
function editCustomerByID(id,customerInfo){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ws = ss.getSheetByName("DATA");
const custIds = ws.getRange(2, 1, ws.getLastRow()-1, 1).getDisplayValues().map(r => r[0].toString().toLowerCase());
const posIndex = custIds.indexOf(id.toString().toLowerCase());
const rowNumber = posIndex === -1 ? 0 : posIndex +2;
Logger.log(customerInfo);
ws.getRange(rowNumber, 2, 1, 8).setValues([[
customerInfo.name,
customerInfo.total,
customerInfo.day1in,
customerInfo.day1out,
customerInfo.day1tot,
customerInfo.day2in,
customerInfo.day2out,
customerInfo.day2tot
// UNTIL DAY 14
]]);
return true;
To skip columns, you can subdivide your range into individual ranges and implement conditional statements
Sample:
function editCustomerByID(id,customerInfo){
...
var valueArray = [
customerInfo.name,
customerInfo.total,
customerInfo.day1in,
customerInfo.day1out,
customerInfo.day1tot,
customerInfo.day2in,
customerInfo.day2out,
customerInfo.day2tot
// UNTIL DAY 14
]
var startColumn = 2;
//loop through all values
for (var i = 0; i < valueArray; i++){
// filter out every 3rd value
if((i+1) % 3 != 0){
ws.getRange(rowNumber, (startColumn + i)).setValue(valueArray[i]);
}
}
return true;
Note that the sample code above uses the method setValue() instead of setValues()
Performing multiple requests to set values to individual ranges is less efficient that setting all values at once within a single request, however in your case it is necessary since your desired value range is not continuos

Vlookup at Google Apps Script with for loop [duplicate]

This question already has answers here:
Google Script version of VLookup (More Efficient Method?)
(2 answers)
Closed 2 years ago.
I need your help please. I would like to do a for loop or something else that works like a Formula =Vlookup
I have two Sheets. in Sheet1 (Overview) there are ID's like 1000, 1002, 1003,...,100X in Column A;
Sheet2 is a Form Response Sheet (Response), where you need to enter your ID and an Action with 'Ok' and 'Nok'. The ID I enter appears in Sheet2 Column B and the Action (Ok/Nok) apperas in Sheet2 Column C.
Now I would like to Copy the Ok/Nok to the Row with the same ID in the Overview sheet with a onFormSubmit function.
for Example. Person with ID 1005 makes a form response with the Action 'Ok'. Now should the 'Ok' copied to the Overview sheet in Column B and in the exact row (in this case: row with the ID 1005).
Here is my function. but I don't want to have formulars in the sheet. so I aked for another solution.
function vlookup() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange(1,5);
cell.setFormula('=ARRAYFORMULA(IFS(ROW(Response!B:B)=1,"Action from
User",Response!B:B="","",TRUE,IFERROR(VLOOKUP(A:A,Response!B:C,2,0),"Waiting for Response")))');
}
Hope someone can help me.
Thank you in advance for your help!
Jonas
Thank you for all these answers. I tryed the code from stackoverflow.com/questions/60255775 – TheMaster and that workes fine!
but it seams very complicated for a programming beginner. exspecially the part with the "Hash".
I also added a second compare and copy to get the data from a Reason if the Nok is used in the Form.
const ss = SpreadsheetApp.getActive();
/**
* #param {GoogleAppsScript.Spreadsheet.Sheet} fromSht -Sheet to import from
* #param {GoogleAppsScript.Spreadsheet.Sheet} toSht -Sheet to import to
* #param {Number} fromCompCol -Column number of fromSht to compare
* #param {Number} toCompCol -Column number of toSht to compare
* #param {Number} fromCol -Column number of fromSht to get result
* #param {Number} toCol -Column number of toSht to get result
*/
function copyToOverview(e,response,
fromSht = ss.getSheetByName('Response'),
toSht = ss.getSheetByName('Overview'),
fromCompCol = 2,
toCompCol = 1,
fromCol = 3,
toCol = 2,
fromColRej = 4,
toColRej = 3
) {
const toShtLr = toSht.getLastRow();
const toCompArr = toSht.getRange(2, toCompCol, toShtLr - 1, 1).getValues();
const fromArr = fromSht.getDataRange().getValues();
fromCompCol--;
fromCol--;
fromColRej--;
/*Create a hash object of fromSheet*/
const obj1 = fromArr.reduce((obj, row) => {
let el = row[fromCompCol];
el in obj ? null : (obj[el] = row[fromCol]);
return obj;
}, {});
/*Create a second hash object of fromSheet to copy the Reason why it is Nok (also from filling out the Form) */
const obj3 = fromArr.reduce((obj2, row) => {
let el1 = row[fromCompCol];
el1 in obj2 ? null : (obj2[el1] = row[fromColRej]);
return obj2;
}, {});
//Paste to column first toSht copy the "ok/nok" second toSht for the Reason why Nok
toSht
.getRange(2, toCol, toShtLr - 1, 1)
.setValues(toCompArr.map(row => (row[0] in obj1 ? [obj1[row[0]]] : [null])));
toSht
.getRange(2, toColRej, toShtLr - 1, 1)
.setValues(toCompArr.map(row => (row[0] in obj3 ? [obj3[row[0]]] : [null])));
}
I also tried the Code from "Michiel the Temp" and it seams, that it also works.
The code from "Mateo Randwolf" looks very simple and I tried it too. Works also very good!
I have modified it a bit and it works like I wish! I think I will use this code.
function onFormSubmit(e) {
// Get the sheet where the form responses are submitted and the one where we want to check the IDs
var formSheet = SpreadsheetApp.getActive().getSheetByName('Response');
var destinationSheet = SpreadsheetApp.getActive().getSheetByName('Overview');
// Get the new incoming data (ID and Ok/Nok) with each form submit by accessing
// the trigger object e which is the submited and new form response row
var submittedId = formSheet.getRange(e.range.getRow(), 2).getValue();
var submittedValue = formSheet.getRange(e.range.getRow(), 3).getValue();
var submittedValueReason = formSheet.getRange(e.range.getRow(), 4).getValue();
// get all the ID values we have in the sheet we want to check them. flat will convert all the returning
// 2D array of values in a 1D array with all the IDs
var idRange = destinationSheet.getRange(1, 1, destinationSheet.getLastRow(),1).getValues().flat();
// iterate over all your IDs
for(i=0;i<idRange.length;i++){
// if one ID is the same as the incoming one from the form response
if(idRange[i] == submittedId){
// set its value to the one submitted by the form
destinationSheet.getRange(i+1, 2).setValue(submittedValue);
}
if(idRange[i] == submittedId){
destinationSheet.getRange(i+1, 3).setValue(submittedValueReason);
destinationSheet.getRange(i+1, 2).getValue() == "Nok" ? destinationSheet.getRange(i+1, 4).setValue("Closed") : destinationSheet.getRange(i+1, 4).setValue("Open");
}
}
}
Thank you all for the Help you are amazing!
So I can do my next step in the Project with updating checkboxes in the Form.
I didn't test this with a trigger but this should work
function vlookup() {
var ssOverview = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Overview");
var ssOverviewLr = ssOverview.getLastRow();
var ssOverviewData = ssOverview.getRange(2, 1, ssOverviewLr, 1).getValues(); //assuming you have a header in the first row
var ssResponse = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Response");
var ssResponseLr = ssResponse.getLastRow();
var newResponse = ssResponse.getRange(ssResponseLr, 2, 1, 2).getValues();
var Ids = ssOverviewData.map(function (r){return r[0];});
for(var i = 0; i < newResponse.length; i++)
{
var row = newResponse[i];
var id = row[0];
var action = row[1];
var index = Ids.indexOf(id);
if(index == -1)
{
SpreadsheetApp.getActiveSpreadsheet().toast("No matches", "Be aware")
}
else
{
ssOverview.getRange(index + 2, 2).setValue(action); //this puts the action in column B
}
}
}
In order to check the IDs every time there is a new form submission and change the data in the ID sheet accordingly you will need to use installable triggers. Specifically you should use a FormSubmit trigger which triggers the function every time there is a form submission. Along with this trigger you will use its event object.
To add an installable trigger, in your Apps Script editor go to Edit -> Current project's triggers and create a new trigger by clicking Add trigger. Make sure that you select On form submit as the event type and that you select the function presented below (so please first copy/paste the function below before creating your trigger).
The following function takes use of this trigger event to compare the incoming data to your Column A of IDs and check for matches and if so it adds the relevant Ok/Nok information. It has self explanatory comments:
function onFormSubmit(e) {
// Get the sheet where the form responses are submitted and the one where we want to check the IDs
var formSheet = SpreadsheetApp.getActive().getSheetByName('Form Responses 1');
var destinationSheet = SpreadsheetApp.getActive().getSheetByName('Check');
// Get the new incoming data (ID and Ok/Nok) with each form submit by accessing
// the trigger object e which is the submited and new form response row
var submittedId = formSheet.getRange(e.range.getRow(), 2).getValue();
var submittedValue = formSheet.getRange(e.range.getRow(), 3).getValue();
// get all the ID values we have in the sheet we want to check them. flat will convert all the returning
// 2D array of values in a 1D array with all the IDs
var idRange = destinationSheet.getRange(1, 1, destinationSheet.getLastRow(),1).getValues().flat();
// iterate over all your IDs
for(i=0;i<idRange.length;i++){
// if one ID is the same as the incoming one from the form response
if(idRange[i] == submittedId){
// set its value to the one submitted by the form
destinationSheet.getRange(i+1, 2).setValue(submittedValue);
}
}
}

Compare objects in array to rows on sheet

first time posting on here so be gentle! Haha.
I'm more than happy to post my current code if my explanation doesn't meet everyones standards.
I have an array which I have ripped data from an API stored in it. Each object in the array contains 3 values;
User name
User ID
Current statistic
Each row on my sheet contains these 3 properties and I want to list the last 30 days of value 3.
So on the first day, a row may contain
A1 "John" B1 "12345" C1 "5"
The 2nd day it would contain
A1 "John" B1 "12345" C1 "20" D1 "5"
3rd day
A1 "John" B1 "12345" C1 "40" D1 "20" E1 "5"
I can do this even with my limited knowledge of loops, what I have issues with is if there is a new entry in the array that is NOT on the sheet I need to be able to add it. Then I can continue adding data every day from there.
Thanks in advance.
function GymStrength1() {
var ss = SpreadsheetApp.openById("1bXc_AZcAAl09bf0ibfC6vQSY9O3ikpn0Ru7pZ4oZ0g8");
var sheet = ss.getSheetByName("Strength");
var currentData = sheet.getDataRange().getValues();
var response = UrlFetchApp.fetch("https://api.torn.com/faction/?selections=basic,contributors&stat=gymstrength&key=NhNhNbCmgGaqx0vQ");
var json = response.getContentText();
var data = JSON.parse(json);
var totalMembers = data["contributors"]["gymstrength"];
var activeMembers = [];
var newData = [];
// Gets active members and their daily contribution
for (var obj in totalMembers){
var playerID = obj;
var currentStat = data["contributors"]["gymstrength"][obj]["contributed"];
var currentMember = data["contributors"]["gymstrength"][obj]["in_faction"];
if (currentMember == "1"){
activeMembers.push([data["members"][obj]["name"],playerID,currentStat])
}
}
// for each active member, check against sheet first
// if row contains member, then merge row with member and paste into newData
// if row doesnt contain member then add member into newData
var copy = false;
for (var member in activeMembers){
for (var i = 1; i < currentData.length; i++){
var row = currentData[i];
// updates each row. works fine
if (activeMembers[member][1] == row[2]){
newData.push([activeMembers[member][0],activeMembers[member][1],activeMembers[member][2],row[3],row[4]]);
copy = true;
}
}
if (!copy){
newData.push([activeMembers[member][0],activeMembers[member][1],activeMembers[member][2],"0","0"]);
}
}
console.log(activeMembers)
console.log(newData)
sheet.getRange(2, 2, currentData.length, currentData[0].length).clearContent();
sheet.getRange(2, 2, newData.length, newData[0].length).setValues(newData);
}
I'm basing the comparison of the newData array and the sheet on the currentStat value of each entry. If the currentStat of an object in activeMembers is NOT on the sheet, I want the name, playerID and currentStat pushed to the newData array. If the currentStat IS on the sheet, it pushes the name, playerID and currentStat to the newData array as well as copying the rest of the values on that row. I can do that.
I am having trouble with adding entries that are NOT on the sheet, updating them works fine.
Brief example:
activeMembers array has obj name, playerID, currentStat
[
['John', '2856', '50'],
['Bob', '2957', '20'],
['Peter', '4579', '80']
]
sheet currently only has
[['John', '2856', '40']]
I would like the end result on the sheet to be
John 2856 50 40
Bob 2957 20
Peter 4579 80
John's 40 value has shifted to the right and the current value put in its place.
Create a map of sheet data: id=>oldStat
Modify the activeMembers array in place using the map data
/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/
const activeMembers = [
['John', '2856', '50'],
['Bob', '2957', '20'],
['Peter', '4579', '80'],
];
const sheetData = [['John', '2856', '40']];
const sMap = new Map();
sheetData.forEach(([, id, oldStat]) => sMap.set(id, oldStat));
activeMembers.forEach(row =>
row.push(sMap.has(row[1]) ? sMap.get(row[1]) : '')
);
console.info({sMap,activeMembers});
console.log(JSON.stringify(activeMembers));
<!-- https://meta.stackoverflow.com/a/375985/ --> <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

Improve function delete rows by contain value

I have working code, that delete row from sheet if column corresponds to one of the conditions. It based on arrays and because of it works much more faster than standard google sheet deleteRow function. I call it like this:
deleteRowsBV('SIZ',4,'0','')
where
deleteRowsBV(listName,ColNum,FirstSearch,SecondSearch)
What I want is a call function with more or less and equal signs, like this:
deleteRowsBV('SIZ',4,<='0',=='')
But in case of my main function, it doesn't work, when I specify a variable instead of a sign and a value.
Here is main function:
function deleteRowsBV(listName,ColNum,FirstSearch,SecondSearch) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(listName);
var DataLengBefore = sheet.getLastRow();
var DataColLeng = sheet.getLastColumn();
var data = sheet.getRange(1,1,DataLengBefore,DataColLeng).getValues();
for (var i = data.length-1; i >= 0; i--) {
if (data[i][ColNum] <= FirstSearch||data[i][ColNum] == SecondSearch) {
data.splice(i, 1);
}
}
sheet.getRange(10, 1, DataLengBefore,DataColLeng).clearContent();
sheet.getRange(10, 1,data.length,5).setValues(data);
}
Based from this related post, spreadsheet rows and columns are numbered starting at 1, for all methods in the SpreadsheetApp, while javascript arrays start numbering from 0. You need to adjust between those numeric bases when working with both. When deleting rows, the size of the spreadsheet dataRange will get smaller; you did take that into account, but because you're looping up to the maximum size, the code is complicated by this requirement. You can simplify things by looping down from the maximum.
You may refer with this thread. However, this only looks at the value from a single cell edit now and not the values in the whole sheet.
function onEdit(e) {
//Logger.log(JSON.stringify(e));
//{"source":{},"range":{"rowStart":1,"rowEnd":1,"columnEnd":1,"columnStart":1},"value":"1","user":{"email":"","nickname":""},"authMode":{}}
try {
var ss = e.source; // Just pull the spreadsheet object from the one already being passed to onEdit
var s = ss.getActiveSheet();
// Conditions are by sheet and a single cell in a certain column
if (s.getName() == 'Sheet1' && // change to your own
e.range.columnStart == 3 && e.range.columnEnd == 3 && // only look at edits happening in col C which is 3
e.range.rowStart == e.range.rowEnd ) { // only look at single row edits which will equal a single cell
checkCellValue(e);
}
} catch (error) { Logger.log(error); }
};
function checkCellValue(e) {
if ( !e.value || e.value == 0) { // Delete if value is zero or empty
e.source.getActiveSheet().deleteRow(e.range.rowStart);
}
}

Resources