I want to know the efficient way to subtract two object key[amount] values based on the action key.
data = [
{ _id: { token: 'BEEF', action: 'received' }, amount: 4 },
{ _id: { token: 'BEEF', action: 'sent' }, amount: 2 },
{ _id: { token: 'GFUN', action: 'received' }, amount: 9},
{ _id: { token: 'HOT', action: 'received' }, amount: 6 },
{ _id: { token: 'HOT', action: 'sent' }, amount: 4 },
{ _id: { token: 'LINK', action: 'received' }, amount: 8},
{ _id: { token: 'METM', action: 'sent' }, amount: 7 },
{ _id: { token: 'METM', action: 'received' }, amount: 9},
{ _id: { token: 'ORTC', action: 'received' }, amount: 5},
{ _id: { token: 'ORTC', action: 'sent' }, amount: 3 }
]
desired result after calculation
[
{ token: 'BEEF', amount: 2 },
{ token: 'GFUN', amount: 9},
{ token: 'HOT' , amount: 2 },
{ token: 'LINK', amount: 8},
{ token: 'METM', amount: 2 },
{ token: 'ORTC', amount: 2},
]
Maybe something like this:
const results = [];
const idx = {};
data.forEach(v => {
const token = v['_id']['token'];
if(!token in idx) {
const item = {
token: token,
amount: 0
};
results.push(item);
idx[token] = item;
}
idx[token].amount +=
v['_id']['action'] == 'received' ? v.amount : -v.amount;
});
results is the final array. idx is a lookup into the array in order to make locating the correct item easier.
There's lots of ways to approach this, but here's what I did.
First break the array into two arrays, one for the sent items and one for the received.
const sent = data.filter(o => o._id.action === 'sent');
const recieved = data.filter(o => o._id.action === 'received');
I'm assuming that there is always an entry in received and there may or may not be an entry in sent. So I mapped from the recieved array to the results. For each entry, I find the corresponding entry in sent, and if there is no matching entry, I subtract 0.
const differences = recieved.map( r => {
const s = sent.find(o => o._id.token === r._id.token );
const sentAmount = s ? s.amount : 0;
return {
token: r._id.token,
amount: r.amount - sentAmount,
}
})
Related
I have this array of objects
const items = [
{
id: '121',
itemDate: '2022-04-28',
itemName: 'testname1',
itemCategory: 'Category A',
itemPrice: { price: '100', currency: 'GBP' },
createdBy: {
username: 'user1',
name: 'Name 1',
date: '2022-04-28T22:41:59',
},
},
{
id: '122',
itemDate: '2022-04-28',
itemName: 'testname2',
itemCategory: 'Category B',
itemPrice: { price: '100', currency: 'GBP' },
createdBy: {
username: 'user2',
name: 'Name 2',
date: '2022-04-28T22:42:44',
},
},
{
id: '122',
itemDate: '2022-04-28',
itemName: 'testname3',
itemCategory: 'Category C',
itemPrice: { price: '200', currency: 'GBP' },
createdBy: {
username: 'user2',
name: 'Name 2',
date: '2022-04-28T22:43:16',
},
},
]
Code I'm using:
items.reduce(function (c, x) {
if (!c[x.createdBy.username])
c[x.createdBy.username] = {
username: x.createdBy.username,
total: 0,
}
c[x.createdBy.username].total += Number(x.itemPrice.price)
return c
}, [])
This part gives me the following output:
items :>> [
user1: { username: 'user1', total: 100},
user2: { username: 'user2', total: 300}
]
So I tried this to get rid of the object names:
let output = []
let totalSum = 0
for (const username in items) {
let temp = {
username: items[username].username,
total: items[username].total,
}
totalSum = totalSum + items[username].total
output.push(temp)
}
output.push({ username: 'allUsers', total: totalSum })
return output
And final output is as I want it now:
output :>> [
{ username: 'user1', total: 100 },
{ username: 'user2', total: 300 },
{ username: 'allUsers', total: 400}
]
My two questions...
Is there a way to update the .reduce part so that I'd get an object without the name at the beggining, without having to use the for loop?
Is there also a way to implement the part that would sum up all the totals?
Thank you
Code Sample (without comments/description)
const groupAndAdd = arr => (
Object.values(
arr.reduce(
(acc, {createdBy : {username}, itemPrice: {price}}) => {
acc.allUsers ??= { username: 'allUsers', total: 0};
acc.allUsers.total += +price;
if (username in acc) {
acc[username].total += +price;
} else {
acc[username] = {username, total: +price};
}
return acc;
},
{}
)
)
);
Presented below is a working demo to achieve the desired objective, with notes/comments to help understand.
Code Snippet
// method to group by user and sum prices
const groupAndAdd = arr => (
// extract the values from the intermediate result-object
Object.values(
arr.reduce( // generate result as object
(acc, {createdBy : {username}, itemPrice: {price}}) => {
// above line uses de-structuring to directly access username, price
// below uses logical nullish assignment to set-up "allUsers"
acc.allUsers ??= { username: 'allUsers', total: 0};
// accumulate the "price" to the all-users "total"
acc.allUsers.total += +price;
// if "acc" (accumulator) has "username", simply add price to total
if (username in acc) {
acc[username].total += +price;
} else {
// create an object for the "username" with initial total as "price"
acc[username] = {username, total: +price};
}
// always return the "acc" accumulator for ".reduce()"
return acc;
},
{} // initially set the "acc" to empty object
)
) // if required, use ".sort()" to move the all-users to last position in array
);
const items = [{
id: '121',
itemDate: '2022-04-28',
itemName: 'testname1',
itemCategory: 'Category A',
itemPrice: {
price: '100',
currency: 'GBP'
},
createdBy: {
username: 'user1',
name: 'Name 1',
date: '2022-04-28T22:41:59',
},
},
{
id: '122',
itemDate: '2022-04-28',
itemName: 'testname2',
itemCategory: 'Category B',
itemPrice: {
price: '100',
currency: 'GBP'
},
createdBy: {
username: 'user2',
name: 'Name 2',
date: '2022-04-28T22:42:44',
},
},
{
id: '122',
itemDate: '2022-04-28',
itemName: 'testname3',
itemCategory: 'Category C',
itemPrice: {
price: '200',
currency: 'GBP'
},
createdBy: {
username: 'user2',
name: 'Name 2',
date: '2022-04-28T22:43:16',
},
},
];
console.log('group and add prices per user: ', groupAndAdd(items));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.
PS: If you'd like to add value to stackoverflow community,
Please consider reading: What to do when my question is answered
Thank you !
For your first question, you're initialising correctly as an array, but you're using just object. Two ways you can do this.
First Option
let something = items.reduce(function(c, x) {
if (!c[x.createdBy.username])
c[x.createdBy.username] = {
username: x.createdBy.username,
total: 0,
}
c[x.createdBy.username].total += Number(x.itemPrice.price)
return c
}, {});
something = Object.values(something);
Second Option
I was thinking of using just push, but seems it's not possible, so the above is the only option.
Using push is possible, but it'll get too complicated by checking with find and updating the correct array element.
For your second question of summing up all the totals, you can use the simple syntax of:
const sum = arr.reduce((a, c) => a + c, 0);
This is the minimum code you need for array of numbers to be summed.
I have it done but I think is not the best. I want to find out a better way (ES6) to loop multiple nested objects and met a condition and return id like below outcome (with and without)
I have two object variable:
let userid= 'samuel17786'
let questions = {
"809992981": {
id: '809992981',
author: 'samuel17786',
timestamp: 746716146716,
optionOne: {
userid: ['samuel17786','john6889'],
food: 'salad',
},
optionTwo: {
userid: [],
food: 'sandwich'
}
},
"740437039": {
id: '740437039',
author: 'john6889',
timestamp: 436716146785,
optionOne: {
userid: ['petter334'],
food: 'rice',
},
optionTwo: {
userid: ['john6889'],
food: 'spaghetti'
}
},
"999937039": {
id: '999937039',
author: 'john6889',
timestamp: 436444456785,
optionOne: {
userid: [],
food: 'fish',
},
optionTwo: {
userid: ['john6889','samuel17786'],
food: 'shrimp'
}
},
}
the outcome needs object id that matches userid within optionOne-->userid and optionTwo-->userid
Need outcome like:
with = ["809992981","999937039"]
without = ["740437039"]
I have tried and its working (not the best way I think):
let without = [];
let with =[];
for (const key in questions) {
//optional check for properties from prototype chain
if (questions.hasOwnProperty(key)) {
let existsInOne = Object.values(questions[key].optionOne.userid).includes(userid);
let existsInTwo = Object.values(questions[key].optionTwo.userid).includes(userid);
if(existsInOne || existsInTwo)
{
with.push(questions[key].id);
}
else if (!existsInOne && !existsInTwo)
{
without.push(questions[key].id);
}
}
}
I would like to know if there a better way to do this with ES6.
Thanks
You could take a single loop and sort the key to an object with boolean keys.
let userid = 'samuel17786',
questions = { 809992981: { id: '809992981', author: 'samuel17786', timestamp: 746716146716, optionOne: { userid: ['samuel17786', 'john6889'], food: 'salad' }, optionTwo: { userid: [], food: 'sandwich' } }, 740437039: { id: '740437039', author: 'john6889', timestamp: 436716146785, optionOne: { userid: ['petter334'], food: 'rice' }, optionTwo: { userid: ['john6889'], food: 'spaghetti' } }, 999937039: { id: '999937039', author: 'john6889', timestamp: 436444456785, optionOne: { userid: [], food: 'fish' }, optionTwo: { userid: ['john6889', 'samuel17786'], food: 'shrimp' } } },
{ true: with_, false: without } = Object.keys(questions).reduce((r, k) => {
r[questions[k].author === userid].push(k);
return r;
}, { true: [], false: [] });
console.log(with_); // needs _ because with is a resereved word
console.log(without);
This is what my array of logged in users looks like:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
}];
I will concat this table when a new user logs in to my system:
this.connectedUsers = [
...this.connectedUsers,
{
...payload,
id: client.id
},
];
Then my array looks like this: (I give this to better understand)
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
}
];
If the user with the uuid like 663-dda updates his point, I perform this method again.
When I leave it as it is, something like this will be done:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}
];
I want to write a very nice (use ES6+) algorithm that first checks if such an object exists in this array (check by id or by user.uuid). If so, update. If not, add a new object. So it should be like this:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}
];
In the code where you're updating the array when a user logs in, you could do like this:
if (!this.connectedUsers.find(user => user.user.uuid === payload.user.uuid) {
this.connectedUsers = [
...this.connectedUsers,
{ ...payload, id: client.id },
];
}
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}];
//let take some sample object
const newObDuplicateUuid = {
user: {
uuid: '663-dda',
points: 6,
},
id: "3333290"
}
const newObDuplicateId = {
user: {
uuid: '756-dda',
points: 6,
},
id: "33332"
}
const newObFresh = {
user: {
uuid: '756-dda',
points: 6,
},
id: "3333290"
}
let checkRule = connectedUsers.every(item => item.user.uuid != newObDuplicateUuid.user.uuid && item.id != newObDuplicateUuid.id)
//return false for same uuid
checkRule = connectedUsers.every(item => item.user.uuid != newObDuplicateId.user.uuid && item.id != newObDuplicateId.id)
//return false for same id
checkRule = connectedUsers.every(item => item.user.uuid != newObFresh.user.uuid && item.id != newObFresh.id)
//return true
console.log('Passed validation :'+checkRule);
const result = checkRule ? [...connectedUsers,newObFresh] : 'There is duplicate value';
console.log(result);
This is my approach. One function that covers both cases. Just pass the array and the new entry and it will return the updated list
function updateOrAddUser(listOfUsers, newEntry) {
let found = false;
const updatedUserList = listOfUsers.map(entry => {
if (entry.user.uuid === newEntry.user.uuid) {
found = true;
return newEntry;
}
return entry;
});
if (!found) {
updatedUserList.push(newUser);
}
return updatedUserList;
}
I have an array of object like this:
let messageScoreData = {
messagescore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 2605.4
},
{
userid: "5bacc98431481e0520856df8",
score: 1013.2
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 41
},
{
userid: "5bc6d0bb26f1bb1b44a790c9",
score: 29
}
],
messagescorebefore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 3754
},
{
userid: "5bacc98431481e0520856df8",
score: 1259.8
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 98
},
{
userid: "5bced078d62b321d08f012af",
score: 22
},
{
userid: "5bcec1ad11302529f452b31e",
score: 6
},
{
userid: "5c10afec8c587d2fac8c356e",
score: 6
},
{
userid: "5c07b7f199848528e86e9359",
score: 3
},
{
userid: "5bed1373f94b611de4425259",
score: 2
},
{
userid: "5c21ccff833a5006fc5a98af",
score: 2
},
{
userid: "5c21ccff82e32c05c4043410",
score: 1
}
]
};
Now we will provide the weight-age value i.e messagescorebefore array have 0.4 value and messagescore have 0.6 value;
For that I have the algorithm which sequentialize the value with weight-age value. i.e
var result = messageScoreData;
var columns = [
{
name: "messagescorebefore",
value: 0.4
},
{
name: "messagescore",
value: 0.6
}
];
var total = {};
for (let column of columns) {
for (let userid of result[column.name]) {
var alphabet = userid.userid;
if (total[alphabet]) {
total[alphabet] += column.value;
} else {
total[alphabet] = column.value;
}
}
}
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value);
console.log(valueholder);
By this Algo output is :
[ { name: '5bacc8c6563a882a1ca7756a', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c6', value: 1 },
{ name: '5bacc98431481e0520856df8', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c9', value: 0.6 },
{ name: '5bcec1ad11302529f452b31e', value: 0.4 },
{ name: '5bced078d62b321d08f012af', value: 0.4 },
{ name: '5c07b7f199848528e86e9359', value: 0.4 },
{ name: '5bed1373f94b611de4425259', value: 0.4 },
{ name: '5c21ccff833a5006fc5a98af', value: 0.4 },
{ name: '5c21ccff82e32c05c4043410', value: 0.4 },
{ name: '5c10afec8c587d2fac8c356e', value: 0.4 } ]
Problem is userid: "5bacc98431481e0520856df8" will come on second position on both array but after final calculation this will come under 3rd position which is wrong.
expected output will be like this:
[ { name: '5bacc8c6563a882a1ca7756a', value: 1 },
{ name: '5bacc98431481e0520856df8', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c6', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c9', value: 0.6 },
{ name: '5bced078d62b321d08f012af', value: 0.4 },
{ name: '5bcec1ad11302529f452b31e', value: 0.4 },
{ name: '5c10afec8c587d2fac8c356e', value: 0.4 },
{ name: '5c07b7f199848528e86e9359', value: 0.4 },
{ name: '5bed1373f94b611de4425259', value: 0.4 },
{ name: '5c21ccff833a5006fc5a98af', value: 0.4 },
]
Any help is really appreciated for this. Thanks in advance
Actually, you want to preserve relative order of elements. normal sort function is not guaranteed to preserve relative order. so we need some tricks to keep relative order like below.
let messageScoreData = {
messagescore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 2605.4
},
{
userid: "5bacc98431481e0520856df8",
score: 1013.2
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 41
},
{
userid: "5bc6d0bb26f1bb1b44a790c9",
score: 29
}
],
messagescorebefore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 3754
},
{
userid: "5bacc98431481e0520856df8",
score: 1259.8
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 98
},
{
userid: "5bced078d62b321d08f012af",
score: 22
},
{
userid: "5bcec1ad11302529f452b31e",
score: 6
},
{
userid: "5c10afec8c587d2fac8c356e",
score: 6
},
{
userid: "5c07b7f199848528e86e9359",
score: 3
},
{
userid: "5bed1373f94b611de4425259",
score: 2
},
{
userid: "5c21ccff833a5006fc5a98af",
score: 2
},
{
userid: "5c21ccff82e32c05c4043410",
score: 1
}
]
};
var result = messageScoreData;
var columns = [
{
name: "messagescorebefore",
value: 0.4
},
{
name: "messagescore",
value: 0.6
}
];
var total = [];
for (let column of columns) {
for (let userid of result[column.name]) {
var alphabet = userid.userid;
if (total[alphabet]) {
total[alphabet] += column.value;
} else {
total[alphabet] = column.value;
}
}
}
let res = Object.keys(total).map((k, idx) => {
return {
name: k,
value: total[k],
index: idx
}
})
var output = res.sort((f, s) => {
if (s.value < f.value) return -1;
if (s.value > f.value) return 1;
return f.index - s.index
})
console.log("output : ", output)
The observed behaviour is expected since you are sorting the values in a descending way: .sort((f, s) => s.value - f.value);. From your example it seems that you want to sort the entries lexicographically on the names. In that case you should sort according to the names:
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => f.name.localeCompare(s.name));
If you want to sort them primarily on the values (descending) and secondarily on the names (ascending) then do:
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value || f.name.localeCompare(s.name));
In this case, if two entries have the same value the difference s.value - f.value will be 0. Since this is a falsy value, f.name.localeCompare(s.name) will be evaluated, effectively sorting the values lexicographically on their name.
If you want to sort the entries based on their values but retain the original order for entries with the same value you can do the following:
const entries = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
const valueholder = entries.sort((f, s) => s.value - f.value || arr.indexOf(f) - arr.indexOf(s));
The reason we need to explicitly sort on their original order is because the built-in sorting algorithm is not (guaranteed to be) stable. Note that the above sorting is not very efficient since we use indexOf. I leave it as an exercise to first loop through the array and accumulate all indexes in a map that maps names to indexes. As such, when sorting you can look up the indexes rather than computing them.
If you're looking for stable sort, i.e. preserving the original order of elements of the array with equal value, you have to add a comparison of the indexes of the key array (assuming that this has the proper ordering):
const keys = Object.keys(total);
const valueholder = keys
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value || keys.indexOf(f.name) < keys.indexOf(s.name));
I am making a MEAN Stack application, and am attempting to create a database of businesses- which requires an empty array to push new instances of the Business model into.
I then want to sort the index of the businesses based on two of the keys- "name" (alphabetically) and "upVotes".
Here is what I have in my business.service file (client side):
var service = {
create: create,
businesses: [],
upVote: upVote,
showAllBiz: showAllBiz,
};
function showAllBiz(){
$http.get("/api/businesses")
.then(function(res) {
service.businesses = res.data;
}, function(err) {
$log.info(err);
});
}
function create(data) {
return $http({
method: 'POST',
url: '/api/businesses',
data: data,
headers: {
'Content-Type': 'application/json'
}
}).then(function(res) {
service.businesses.push(res.data);
});
}
I also tried to sort() on the back end, with no results. Here is what that looks like:
var Business = require("../models/business");
var nodemailer = require("nodemailer");
var transporter = nodemailer.createTransport();
var firstBy = require("thenby");
function index(req, res) {
if(req.query.search){
Business.find({}).then(function(data) {
var reg = new RegExp(req.query.search, "i");
data = data.filter(function(biz) {
if(reg.test(biz.name)) return biz
})
res.json(data);
}, function(err) {
res.json(err);
});
} else{
Business.find({}).then(function(data) {
res.json(data);
}, function(err) {
res.json(err);
});
}
}
function create(req, res) {
var business = new Business();
console.log(req.body);
business.name = req.body.name;
business.address1 = req.body.address1;
business.address2 = req.body.address2;
business.email = req.body.email;
business.twitterHandle = req.body.twitterHandle;
business.upVote = req.body.upVote;
business.save(function(err, savedBusiness) {
if (err) {
res.send(err)
}
res.json(savedBusiness);
});
I am getting stuck on the fact that I need the empty array for the new instances (in my services), but I also need to make use of the objects within the array in the .sort() method to access the keys (which I would like to sort).
I played with Teun's thenBy.js but was a bit out of my depth.
I have googled sorting arrays and arrays of objects, but these are all examples of sorting information that exists, not information that does not yet exist, thus necessitating the empty array.
Assuming I understand the question, here's the gist of it with some made up data. I left the sort function a bit more verbose to (hopefully) increase readability.
Note that in the sorter method we are comparing first name, then upVotes, then only returning 0 afterwards to signify object equality. Since we are comparing name first followed by upVotes, this is the equivalent of sorting by name, then upVotes.
let arr = [
{ id: 1, name: 'Dominos', upVotes: 21 },
{ id: 2, name: 'Pizza Hut', upVotes: 31 },
{ id: 3, name: 'Pizza Hut', upVotes: 35 },
{ id: 4, name: 'Dominos', upVotes: 61 },
{ id: 5, name: 'Dominos', upVotes: 2 },
{ id: 6, name: 'Pizza Hut', upVotes: 25 },
{ id: 7, name: 'Dominos', upVotes: 10 },
{ id: 8, name: 'Pizza Hut', upVotes: 3 }
];
function sorter(a, b) {
if (a.name > b.name)
return 1;
if (a.name < b.name)
return -1;
if (a.upVotes > b.upVotes)
return 1;
if (a.upVotes < b.upVotes)
return -1;
return 0;
}
arr.sort(sorter);
console.log(arr);
/* [ { id: 7, name: 'Dominos', upVotes: 2 },
{ id: 8, name: 'Dominos', upVotes: 10 },
{ id: 5, name: 'Dominos', upVotes: 21 },
{ id: 6, name: 'Dominos', upVotes: 61 },
{ id: 4, name: 'Pizza Hut', upVotes: 3 },
{ id: 3, name: 'Pizza Hut', upVotes: 25 },
{ id: 1, name: 'Pizza Hut', upVotes: 31 },
{ id: 2, name: 'Pizza Hut', upVotes: 35 } ] */
[].sort(sorter); // no errors. if the array is empty, the callback will never be run.