I have an array containing a customer ID, value of transaction, and transaction ID for each transaction performed by the customer.
I have 20,000 transactions performed by 9,000 customers.
I want one customer ID, an array of all the prices per that customer ID, and an array of all the transaction Ids per customer ID.
Currently looks like this:
var transactionArray =
{
customerId: '1',
price: [ 100 ],
transactionID: ['00a13']
},
{
customerId: '2',
price: [ 200 ],
transactionID: ['00a14']
},
{
customerId: '1',
price: [ 700 ],
transactionID: ['00a15']
},
{
customerId: '2',
price: [ 1700 ],
transactionID: ['00a16']
},
... 19996 more items
and I'd like it to look like this:
var customerArray =
{
customerId: '1',
price: [ 100, 700 ],
transactionID: ['00a13', '00a15']
},
{
customerId: '2',
price: [ 200, 1700 ],
transactionID: ['00a14', '00a16']
},
...8998 more items
Just using reduce and push the elements onto the array
var transactionArray = [{
customerId: '1',
price: [100],
transactionID: ['00a13']
},
{
customerId: '2',
price: [200],
transactionID: ['00a14']
},
{
customerId: '1',
price: [700],
transactionID: ['00a15']
},
{
customerId: '2',
price: [1700],
transactionID: ['00a16']
},
]
var results = Object.values(transactionArray.reduce((custs, { customerId, price, transactionID }) => {
var customer = custs[customerId]
if (!customer) {
custs[customerId] = {
customerId: customerId,
price: [...price],
transactionID: [...transactionID]
}
} else {
customer.price = [...customer.price, ...price]
customer.transactionID = [...customer.transactionID, ...transactionID]
}
return custs
}, {}))
console.log(results)
It's convenient that customerId's are integers. If that ever changes, then you will need to index them, then rebuild the object.
// create a separate array for holding order of customerIds
const customerIds = []
const result = transactionArray
.reduce((acc, { customerId, price, transactionID }) => {
const idIndex = customerIds.indexOf(customerId)
// check if customerId exists in customerIds array
if (idIndex < 0) {
// if it doesn't, add it to the customerId's array
customerIds.push(customerId)
// then add the matching price and transactionID to the accumulator
// of this reduce, spreading the contents of the array into 2 new arrays.
acc.push([[...price], [...transactionID]])
} else {
// if the customerId is already accounted for, then
// add the price and transactionID to that customer's bucket
// at the same index where the customerId exists inside customerIds
acc[idIndex][0].push(...price)
acc[idIndex][1].push(...transactionID)
}
return acc
}, [])
// finally, convert the arrays back into objects
.map((value, index) => {
return ({
customerId: customerIds[index],
price: value[0],
transactionID: value[1],
})
})
console.log(result)
which logs:
[
{
customerId: '1',
price: [ 100, 700 ],
transactionID: [ '00a13', '00a15' ]
},
{
customerId: '2',
price: [ 200, 1700 ],
transactionID: [ '00a14', '00a16' ]
}
]
If the customerIds were strings that didn't represent integers, this will still work -- for example, if your customer data looked like this:
const transactionArray = [
{
customerId: '324asdrea',
price: [ 100 ],
transactionID: ['00a13']
},
{
customerId: '4hdffgi2',
price: [ 200 ],
transactionID: ['00a14']
},
{
customerId: '324asdrea',
price: [ 700 ],
transactionID: ['00a15']
},
{
customerId: '4hdffgi2',
price: [ 1700 ],
transactionID: ['00a16']
}
]
which results in:
[
{
customerId: '324asdrea',
price: [ 100, 700 ],
transactionID: [ '00a13', '00a15' ]
},
{
customerId: '4hdffgi2',
price: [ 200, 1700 ],
transactionID: [ '00a14', '00a16' ]
}
]
Related
I have a data model which looks like this, so each documents has services array and each service contains an items array and I want to update properties in items array.
{
services: [
{
id: '1',
name: 'Service 01',
items: [
{
id: '1',
name: '',
qty: 10
},
{
id: '2',
name: '',
qty: 10
},
]
},
{
id: '2',
name: 'Service 02',
items: [
{
id: '3',
name: '',
qty: 10
},
{
id: '4',
name: '',
qty: 10
},
]
},
]
}
I want to set all the quantities inside services -> items to 0, What will be query for doing it I tried to do,
updateMany({}, { $set: { 'services.items.qty': 0 } });
but it's not working.
Let me know if you need more details.
the all positional operator $[] operator can be used to update all elements
playground
db.collection.update({},
{
$set: {
"services.$[].items.$[].qty": 0
}
})
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 a object
let data1 =
{
_id: "61d576ecb87f099d033a1930",
name: 'Milk',
quality: 'premium',
price: 10,
quantity: 10,
bagSize: '10',
bagCount: 10,
status: 'Process',
sellerDetails: [ [Object] ],
image: '/uploads/milk.jpg'
}
and I have array of objects
let data2 = [
{
_id: "61d576ecb87f099d033a1930",
name: 'Milk',
quality: 'Premium',
price: 10,
quantity: 10,
bagSize: '10',
bagCount: 10,
status: 'Process',
sellerDetails: [ [Object] ],
image: '/uploads/premium.jpg'
},
{
_id: "61d576ecb87f099d033a1931",
name: 'Haldi',
quality: 'Eagle',
price: 10,
quantity: 10,
bagSize: '10',
bagCount: 10,
status: 'Process',
sellerDetails: [ [Object] ],
image: '/uploads/rai.jpg'
}
]
Now I want to filter out data1 value from data2 so the expected result after filter should be
let data2 = [
{
_id: "61d576ecb87f099d033a1931",
name: 'Haldi',
quality: 'Eagle',
price: 10,
quantity: 10,
bagSize: '10',
bagCount: 10,
status: 'Process',
sellerDetails: [ [Object] ],
image: '/uploads/rai.jpg'
}
]
I have tried,
function filteredData(data1,data2){
const filtered = data1._id !== data2._id
return filtered
}
const filteredArr = data2.filter(filteredData)
Also I have referred this
How can I acheive my expected result, am I doing something completely wrong ?
The following probably does what you want (untested).
Read up on the filter() documentation #: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
and map() #: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
data2.filter(el => {
return data1._id !== el._id;
}
Javascript: How to dynamically create a deeply nested object from an array of objects?
I could achieve one level of separation, but the code has got quite complicated and am not able to figure out how to achieve this for 2nd level.
Actual:
[{
brandId: 1,
spec_desc: "engines",
av: 3000
tv: 1000,
brandName: "bmw",
id: 1,
group: "cars",
cost: 20000.00,
currency: "USD",
desc: "manufacturing costs"
},
{
brandId: 1,
spec_desc: "brakes",
av: 1000,
tv: 2000,
brandName: "bmw",
id: 1,
....
},
{
brandId: 2,
spec_desc: "engines",
av: 1800,
tv: 2500,
brandName: "audi",
id: 2
....
}
]
Expected:
[{
group: "cars",
id: 1,
brands: [{
brandId: 1,
brandName: "BMW",
specs: {
power: [{
spec_desc: "engines",
av: 3000,
tv: 1000
},
{
spec_desc: "brakes",
av: 1000,
tv: 2000
}
],
cost: {
desc: "manufacturing costs",
value: 20000.00,
currency: "USD"
}
}
},
{
brandId: 2,
brandName: "audi",
specs: {
power: [
...
],
}
}
]
},
group: "bikes",
id: 2,
brands: [
....
]
]
Here is what I have tried, but able to obtain grouping only till brandName i-e one level.
function genrows(groups, groupKey) {
return _.toPairs(groups).map(([key, units]) => ({
[groupKey]: key,
units
}))
}
function gengroups(arr, iteratee, key) {
const grouped = _.groupBy(arr, iteratee)
return genrows(grouped, key)
}
function grouparray(units, props) {
let result = [{
units
}]
props.forEach((prop, i) => {
const key = prop
const iteratee = prop.iteratee || prop
result = _.flatten(
result.map(row => {
return gengroups(row.units, iteratee, key).map(group =>
// {...row, ...{ [key]: group[key], units: group.units }}
({ ...row,
[key]: group[key],
units: group.units
}),
)
}),
)
})
return _.flatten(result)
}
const groups = ['brandName', 'id'] //group by key names
// it fetches out these group tags to generate keys,
const desired = grouparray(actual, groups);
Can anyone help me work out how to achieve this dynamically? If you've got this far thanks very much for taking the time to read even if you can't help.
PS: Let me know for further clarifications, my result object & also have used lodash functions.
You could take a classic approach by storing the last group, where an id is found and group the brands with this object.
var data = [{ brandId: 1, spec_desc: "engines", av: 3000, tv: 1000, brandName: "bmw", id: 1, group: "cars", cost: 20000.00, currency: "USD", desc: "manufacturing costs" }, { brandId: 1, spec_desc: "brakes", av: 1000, tv: 2000, brandName: "bmw" }, { brandId: 2, spec_desc: "engines", av: 1800, tv: 2500, brandName: "audi" }],
lastGroup,
result = data.reduce((r, { brandId, spec_desc, av, tv, brandName, id, group, cost: value, currency, desc }) => {
if (id !== undefined) r.push(lastGroup = { group, id, brands: [] });
var brand = lastGroup.brands.find(q => q.brandId === brandId);
if (!brand) lastGroup.brands.push(brand = { brandId, brandName, specs: { power: [], cost: { desc, value, currency } } });
brand.specs.power.push({ spec_desc, av, tv });
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have the following code in my async function
async function test () {
try {
const aucs = await auctions.find({owner: 'owner', place: 'place'}).limit(15);
const item = await Promise.all(aucs.map(async aucs => {
const map = await items.find({id: aucs.item});
return map[0]
}));
--->we are here [1]
} catch (err) {
console.log(err);
}
}
test();
and the point [1] I have two arrays avaliable which contain another objects (both are responces from Mongo) here they are:
aucs = [ { _id: 5c00faa4936359120ceb3632,
auc: 177215422,
item: 130251,
price: 26000000,
lastModified: 1543567955000,
date: 2018-11-30T08:53:56.290Z,
__v: 0 },
{ _id: 5c00faa4936359120ceb363f,
auc: 177215440,
item: 130251,
price: 26000000,
lastModified: 1543567955000,
date: 2018-11-30T08:53:56.290Z,
__v: 0 },... ]
and
item = [ { _id: 5bcd8a6134cdd1223cd3239b,
id: 130251,
name: 'TEST_NAME_1',
__v: 0 },
{ _id: 5bcd8a6134cdd1223cd3239b,
id: 130252,
name: 'TEST_NAME_2',
__v: 0 },...]
And I'd like to add to aucs[i]every element in aucs, item[i].name (name: 'TEST_NAME_1')
Like:
combined = [ { _id: 5c00faa4936359120ceb3632,
auc: 177215422,
item: 130251,
name: 'TEST_NAME_1',
price: 26000000,
lastModified: 1543567955000,
date: 2018-11-30T08:53:56.290Z,
__v: 0 },...]
I'm trying to use for loop with auc[i].name = item[i].name or using aucs.push() but for some unknown reason it wasn't worked for me.
I receive error for .push is not a function and for loop didn't add anything. So maybe someone have any idea?
Note: 1
actually solve one problem with item, mongo returns me array inside array like [ [ { ...} ] ] so I should using return map[0] to fix it.
Note: 2
both of aucs and item are object according to typeof and have .length option (they are both the same length and should be all the time. So they are not promises
Note: 3
let merged = {...aucs, ...item}; returns me
{ '0': { _id: 5bcd8a6134cdd1223cd3239b,
id: 130251,
name: 'JewelCraft',
icon: 'inv_jewelcrafting_70_jeweltoy',
stackable: 1,
isAuctionable: true,
__v: 0 }...
but not what I need to
Would be more superior and faster if you use some aggregation trick here
auctions.aggregate([
{ "$match": { "owner": "owner", "place": "place" }},
{ "$lookup": {
"from": "items",
"let": { "item": "$item" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$id", "$$item"] }}}
],
"as": "item"
}},
{ "$limit": 15 }
])
If I understand correctly, the aim is to create a new collection of aucs like the ones found, each updated to include an item name from the item collection where the item has a matching id.
let aucs = [ { _id: "5c00faa4936359120ceb3632",
auc: 177215422,
item: 130251,
price: 26000000,
lastModified: 1543567955000,
date: "2018-11-30T08:53:56.290Z",
__v: 0 },
{ _id: "5c00faa4936359120ceb363f",
auc: 177215440,
item: 130251,
price: 26000000,
lastModified: 1543567955000,
date: "2018-11-30T08:53:56.290Z",
__v: 0 } ];
item = [ { _id: "5bcd8a6134cdd1223cd3239b",
id: 130251,
name: 'TEST_NAME_1',
__v: 0 },
{ _id: "5bcd8a6134cdd1223cd3239b",
id: 130252,
name: 'TEST_NAME_2',
__v: 0 }];
// create a new collection of aucs modified to include the names of matching items
let combined = [];
aucs.forEach(auc => {
let combinedAuc = Object.assign({}, auc);
combined.push(combinedAuc);
let matchingItem = item.find(i => auc.item === i.id);
if (matchingItem) combinedAuc.name = matchingItem.name
});
console.log(combined)