sum a field value in an object array - javascript

Grabbing data from a csv file then assigning to an object array, SmartPostShipments []. Outputting the "total" number of elements in the array is pretty straightforward by using the .length property, but I also need to get the sum of each field value in the object array called ['Net Charge Amount']. What is the best way to accomplish this?
I am fine with using console.log() for this illustration.
const { parse } = require('csv-parse')
const fs = require('fs')
const SmartPostShipments = []
function isSmartpost(shipment) {
return shipment['Service Type'] === 'SmartPost'
}
fs.createReadStream('test.csv')
.pipe(parse({
columns: true
}))
.on('data', (data) => {
if (isSmartpost(data)) {
SmartPostShipments.push(data)
}
})
.on('error', (err) => {
console.log(err)
})
.on ('end', () => {
// This outputs each individual 'net charge amount', but how do I just display the sum
// total?
console.log(SmartPostShipments.map((shipment)=> {
return shipment['Net Charge Amount']
}))
console.log(`${SmartPostShipments.length} Smartpost shipments.`)
})
A couple of the code suggestions look good, but the data is appending instead of summing. Here's a pic of the output:
console.log(SmartPostShipments.map((shipment)=> {
return shipment['Net Charge Amount']
}))
const sum = SmartPostShipments.reduce((partialSum, shipment) => partialSum + shipment['Net Charge Amount'], 0)
console.log(sum)
const sumSP = SmartPostShipments.reduce((cur, pre)=> {
return cur+=pre['Net Charge Amount'];
},0)
console.log(sumSP)

const sum = SmartPostShipments.reduce((cur, pre)=> {
return cur+=pre['Net Charge Amount'];
},0)
console.log(sum)
Please try the above. Hope it works.

This is a variation of this question.
Applied to your situation the code would be:
const sum = SmartPostShipments.reduce((partialSum, shipment) => partialSum + shipment['Net Charge Amount'], 0)

Related

How to filter an array by two indvidual strings from an object?

I'm working on a project where I need to filter 13 items by two different select box values, and I'm getting stuck on persisting the filter.
I have two select boxes that I've selected like so:
let pickupLocation = document.querySelector("#pa_location"); //values are 'complete-set', 'neck', 'bridge'.
let pickupType = document.querySelector("#pa_type1"); // Values are 'soapbar', 'dogear', 'short'.
What's Working:
I'm initializing an object like so:
const activeFilters = {};
To populate the values like so:
//Persist the Complete Set / Single
pickupLocation.addEventListener("change", function () {
if (pickupLocation.value === "complete-set") {
activeFilters.location = "set";
} else {
activeFilters.location = "single";
}
});
pickupType.addEventListener("change", function () {
if (pickupType.value === "soapbar") {
activeFilters.type = "soapbar";
} else if (pickupType.value === "dogear") {
activeFilters.type = "dogear";
} else {
activeFilters.type = "short";
}
});
// Returns something like
// {location: single, type: dogear}
I'm trying to filter an array of input elements by their value. I have 13 inputs each with a value containing words like set, single, dogear, soapbar etc.
Where I'm stuck:
I have a filter function that I'm trying to filter the values of these inputs by two values of the activeFilters object:
const performFilter = (covers) => {
let results;
let filteredValues = Object.values(activeFilters);
filteredValues.forEach((value) => {
results = covers.filter((cover) => cover.value.indexOf(value) !== -1);
});
return results;
};
The problem is my function is returning only one of the two words. For instance, if the my activeFilters object is {location: set, type: dogear} the filtered results array contains only one of them. Where am I going wrong?
Edit:
This function returns all inputs that match one of the activeFilters, and I apologize if I wasn't clear above, but I'd like it to match ALL of the Active Filters. Is this possible with the function below?
const performFilter = (covers) => {
let results = []; // initialise the array
let filteredValues = Object.values(activeFilters);
filteredValues.forEach((value) => {
let res = covers.filter((cover) => cover.value.indexOf(value) !== -1);
results.push(...res);
});
console.log(results);
};
CODEPEN:
Codepen!
const performFilter = (covers) => {
let results = []; // initialise the array
let filteredValues = Object.values(activeFilters);
filteredValues.forEach((value) => {
let res = covers.filter((cover) => cover.value.indexOf(value) !== -1);
// push the value it find individually
// you were overriding the previous value with result = filter()
results.push(...res);
});
return results;
};
// according to Edited question
const performFilter = (covers) => {
let results = []; // initialise the array
let filteredValues = Object.values(activeFilters);
return covers.filter((cover) => filteredValues.every(value => cover.value.indexOf(value) !== -1));
};
I'm not sure if I understood clearly your question, so feel free to comment it.
First, I suggest you to filter your covers array and inside the filtering function iterate through your selected filters. This is because the filter function returns the array already filtered and so you don't need to assign it to a result variable or things like that. So based on that, try this:
const performFilter = (covers) => {
let results;
let filteredValues = Object.values(activeFilters);
const filteredCovers = covers.filter((cover) => {
return cover.value.split("-").some((tag) => filteredValues.includes(tag))
});
console.log(filteredCovers)
};

Updating data in an Object using Firebase arrayUnion

I am looking to update data in an object without changing the index of the object within the array it is contained. As it currently stands, the code removes the current object from the array and then applies array Union to update the array but pushes the component to the end of the array but I am looking to just update the data without the component losing its index. This is the code I am currently working with, I looked through the Firebase docs to see if there was a way to just update the component but couldn't find anything if anyone could point me in the right direction, please.
await firestore.update(project, {
pages: firestore.FieldValue.arrayRemove(page),
});
await firestore.update(project, {
pages: firestore.FieldValue.arrayUnion(newPage),
});
Unfortunately there is no field transform to replace a value like this:
firestore.FieldValue.arrayReplace(page, newPage);
Storing arrays and making changes by index in remote databases is generally discouraged. This older Firebase blog post covers some of the reasons why even though it was written with the Firebase Realtime Database in mind.
If the order of that array is important, you have two options:
fetch the array, mutate it, and then write it back. (simple)
fetch the array, find the relevant index, update that index only. (difficult)
To achieve the first result, you would make use of a transaction to find the previous value and replace it:
const db = firebase.firestore();
const projectDocRef = db.doc("projects/projectId");
function replacePage(oldPage, newPage) {
return db.runTransaction(async (t) => {
const snapshot = await t.get(projectDocRef);
if (!snapshot.exists) {
// no previous data, abort.
return "aborted";
}
const pagesArray = snapshot.get("pages");
const index = pagesArray.findIndex((page) => page === oldPage);
if (index === -1)
return "not-found";
pagesArray[index] = newPage;
await t.set(projectDocRef, { pages: pagesArray }, { merge: true });
return "replaced";
});
}
replacePage("index", "shop")
.then((result) => console.log("Page replacement was " + (result === "replaced" ? "" : " not") + " successful"))
.catch((err) => console.error('failed: ', err));
Note: Anything beyond this point is educational. There are many issues with this approach at scale.
Because Firestore doesn't support array entry replacement by index, you'll need to implement a way to update an index using something Firestore understands - maps. Using some FirestoreDataConverter trickery, you can use the converter to serialize your array as a map when you write it to Cloud Firestore and deserialize it back to an array when you read it. The major trade-off here is in how you will be able to query your data. You will be able to perform queries by index (such as where('pages.0', '==', 'shop')) but you'll lose the ability to use array-contains queries (such as where('pages', 'array-contains', 'shop')).
First, you need to define the converter:
// const obj = {};
// setNestedProperty(obj, ["a", "b", "c"], true)
// obj is now { "a": { "b": { "c": true } } }
function setNestedProperty(originalObj, pathPropsArray, val) {
const props = pathPropsArray.slice(0, -1);
const lastProp = pathPropsArray[pathPropsArray.length-1];
const parent = props.reduce((obj, p) => obj[p] ? obj[p] : (obj[p] = {}), originalObj);
parent[lastProp] = val;
}
const pagesArrayConverter = {
toFirestore(data) {
if (data.pages !== undefined) {
// step 1) convert array to map
const pagesAsMap = {};
data.pages.forEach((page, index) => {
if (page !== undefined) {
pagesAsMap[index] = page;
}
});
data.pages = pagesAsMap;
// step 2) if there are any mutations to "pages"
// while you are changing it, make the
// changes now before uploading to Firestore
Object.keys(data)
.filter(k => k.startsWith("pages."))
.forEach(k => {
const nestedValue = data[k];
data[k] = undefined;
delete data[k];
setNestedProperty(pagesAsMap, k.slice(6).split("."), nestedValue);
});
}
return data;
},
fromFirestore(snapshot, options) {
const data = snapshot.data(options);
if (data.pages !== undefined) {
const pagesAsArray = [];
Object.entries(data.pages)
.map(([index, page]) => pagesAsArray[index] = page);
// `pagesAsArray` may have empty elements, so we need
// to fill in the gaps with `undefined`:
data.pages = Array.from(pagesAsArray);
}
return data;
}
};
Which you would then attach to a query/reference like this:
const db = firebase.firestore();
const projectDocRef = db.doc("projects/projectId")
.withConverter(pagesArrayConverter)
If you already know that the previous value has an index of 2, you can just use:
await projectDocRef.set({ "pages.2": newPage }, { merge: true });
If you need to find it like before, you can use a transaction:
function replacePage(oldPage, newPage) {
return db.runTransaction(aysnc (t) => {
const snapshot = await t.get(projectDocRef);
if (!snapshot.exists) {
// no previous data, abort.
return "missing";
}
const data = snapshot.data();
// data is a { pages: Page[] }
const index = data.pages.findIndex((page) => page === oldPage);
if (index === -1)
return "not-found";
await t.set(projectDocRef, { ["pages." + oldIndex]: newPage }, { merge: true });
return "replaced";
});
}
replacePage("index", "shop")
.then((result) => console.log("Page replacement was " + (result === "replaced" ? "" : " not") + " successful"))
.catch((err) => console.error('failed: ', err));
arrayUnion adds new items to the array and arrayRemove removes items from an array. There isn't any way to update an existing item in array directly.
You would have to fetch the document, manually add/update the item at relevant index and then update the whole array back to the document.

Optimal method to replace string of IDs with different names using map in javascript

I have a set up of 10 "tags" and each has an ID (format: 00:00:00:xx:xx:xx) and translatedID (format: TXT). I get strings listing violating tags which I want to convert from translatedID to ID.
I have a map of the values defined:
Map(10) {
'00:00:00:02:28:47' => 'T7T',
'00:00:00:02:89:70' => 'T3T',
'00:00:00:02:89:51' => 'T4T',
'00:00:00:02:27:bd' => 'T5T',
'00:00:00:02:89:31' => 'T6T',
'00:00:00:02:89:a0' => 'T1T',
'00:00:00:02:89:af' => 'T2T',
'00:00:00:02:89:4d' => 'T9T',
'00:00:00:02:89:28' => 'T10T',
'00:00:00:02:89:a1' => 'T8T'
}
Example:
input = 'T4T____T5T____T2T____T10T____T6T____'
(desired) output = '00:00:00:02:89:51, 00:00:00:02:27:bd, 00:00:00:02:89:af, 00:00:00:02:89:28, 00:00:00:02:89:31'
My first thought is to loop through each value within the map, see if it exists within input and if so then add the corresponding ID to the output. But this would not be optimal due to unnecessary extra looping. Any suggestions?
Also, my code is using a global variable to use define the map variable. I know this is frowned upon, is there a better way for me to do this? Format of my code below:
let dict = new Map()
function customQueryString() {
return new Promise((resolve, reject) => {
client.query(query, (err, res) => {
res.rows.forEach(psqltag => {
dict.set(psqltag.m_violator_tag, psqltag.m_translatedid)
});
resolve(queryStringAddition) // defined elsewhere doesnt matter for this problem
})
})
}
function loopingQueryFunction(loopQuery) {
client.query(loopQuery, (err, res) => {
res.rows.forEach(tag => {
input = tag.violating_tags // where the input string is found
output = ??? // where the translation needs to happen
}
}
}
async function asyncCall() {
let qStringAddition = await customQueryString();
loopQuery = loopQuery + qStringAddition
for (let i = 0; i< 100; i++) {
console.log("loop " + i)
await delay(1000)
loopingQueryFunction(loopQuery)
}
}
You can use regular expression to get all translated IDs. Let's assume they are all in T<number>T format, which appears true.
const input = 'T4T____T5T____T2T____T10T____T6T____';
const dictEntries = [...dict.entries()].map(([id, translatedId]) => ({id, translatedId}));
const output = input
.match(/T\d+T/g)
.map(translatedId => dictEntries.find(entry => entry.translatedId === translatedId))
.filter(entry => entry !== undefined)
.map(entry => entry.id);
// ["00:00:00:02:89:51", "00:00:00:02:27:bd", "00:00:00:02:89:af", "00:00:00:02:89:28", "00:00:00:02:89:31"]
You can get output.join(', ') if you want it as a comma-separated string.

Return array value from produce function | immer.js

I am using immer.js to perform operations on arrays in the state.
Arrays: basicRecipe and recipeBasicRecipe.
I am modifying the draft.basicRecipe in the produce function. My objective is to return the updated "draft.basicRecipe" value and store the same in temparray1.
let temparray1 = produce(state, draft => {
draft.basicRecipe = draft.basicRecipe.map(item => {
let element = draft.recipeBasicRecipes.find(e => e._id === item._id);
console.log(element);
if (element) {
item.details = item.details.map(e => {
let detail = element.details.find(d => d._id === e._id);
if (detail) {
e.rate = detail.rate;
}
return e;
});
}
return item;
});
return draft.basicRecipe;
});
console.log(temparray1);
When I return the draft I am able to see updated basicRecipe nested in output.
I am getting the below error when I try to return the array i.e draft.basicRecipe
[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft
This code is a mess. You are using map which returns a new array but you're also trying to mutate the original draft object.
This is still unreadable and confusing, but at least by using forEach instead of map we are just mutating and not trying to do two things at once.
let temparray1 = produce(state, (draft) => {
draft.basicRecipe.forEach((item) => {
let element = draft.recipeBasicRecipes.find((e) => e._id === item._id);
if (element) {
item.details.forEach((e) => {
let detail = element.details.find((d) => d._id === e._id);
if (detail) {
e.rate = detail.rate;
}
});
}
});
});

foreach loop not iterating over string array

I'm trying to use a for each loop to check if a user's ID is in a blacklist group I created. When I try to iterate over the string array of userID's, it says blacklisted.forEach is not a function. Why is that?
query = { messageTrackingId: req.query.messageId };
messageId = true;
Messages.find(query)
.populate("creator", "username")
.then(documents => {
console.log("documents is");
console.log(documents[0].creatorId);
let otherUser;
if (documents[0].creatorId === req.query.creatorId) {
console.log("ITS A MATCH!")
otherUser = documents[0].recipientId;
}
else if (documents[0].recipientId === req.query.creatorId) {
console.log("ITS not a match!")
otherUser = documents[0].creatorId;
}
let blacklisted = false;
User.find({ _id: otherUser }).select("blacklistGroup").then((res) => {
blacklisted = res[0].blacklistGroup;
console.log("BLACKLIST SERVER RESPONSE");
console.log(blacklisted);
blacklisted.forEach(function(entry) {
console.log(entry);
});
CONSOLE OUTPUT
documents is
5e52cca7180a7605ac94648f
ITS not a match!
BLACKLIST SERVER RESPONSE
[ '5e52e8af484eba456ca9e814',
'5e52f2cc673de71f60019c76',
'5e52f316673de71f60019c77' ]
(node:12992) UnhandledPromiseRejectionWarning: TypeError: blacklisted.forEach is not a function
I'm not sure why it would display as an array if it's an object, but have you tried creating a new array from it and iterating over that? For example:
blacklisted = [...res[0].blacklistGroup];
blacklisted.forEach(function(entry) {
console.log(entry);
});
Is you "User" statement acting like a fetch? If so, you may have to convert your response to json before using it. Something like...
User.find({ _id: otherUser }).select("blacklistGroup")
.then(res => res.json())
.then(json => {
blacklisted = json.blacklistGroup;
console.log("BLACKLIST SERVER RESPONSE");
console.log(blacklisted);
});
});
I realized that for some reason when I console.log(res[0].blacklistGroup)
it was returning
[ '5e52e8af484eba456ca9e814',
'5e52f2cc673de71f60019c76',
'5e52f316673de71f60019c77' ]
which is also return type Object. So to solve this, I did the following:
let MyBlacklist = JSON.stringify(res[0].blacklistGroup);
MyBlacklist = JSON.parse(MyBlacklist);
then I was able to loop through
let counter = 0;
for (let i = 0; i < MyBlacklist.length; i++) {
counter++;
console.log(MyBlacklist[i]);
console.log(counter);
}
OUTPUT:
5e52e8af484eba456ca9e814
1
5e52f2cc673de71f60019c76
2
5e52f316673de71f60019c77
3

Categories

Resources