I have following array with me where records are shown per user
let data = [
{ userid: 1, placename: abc, price: 10 },
{ userid: 1, placename: pqr, price: 20 },
{ userid: 1, placename: xyz , price: 30},
{ userid: 2, placename: abc , price: 40},
{ userid: 2, placename: lmn , price: 50}
So, I want to transpose this data group by userid, by concatenation of place name and sum of price.
It should be look like below
UseId PlaceName Price
1 abc,xyz,pqr 60
2 abc,lmn 90
And I am binding this data to bootstrap vue b-table component
As of now I have done like this
groupby: function(array, key) {
const result = {};
array.forEach(item => {
if (!result[item[key]]) {
result[item[key]] = [];
}
result[item[key]].push(item);
});
return result;
},
And calling this while view is getting initialized,
groupby(this.list,'userid');
though I am getting all records in row as per user, but I am not able to concatenate the values and doing total.
While binding to table it is giving error 'Expected Arrat,got object'
Is anything missing here !
Any help on this appreciated !
Try out to filter the items that have the same userid then join place names and sum their prices :
array.forEach(item => {
if (!result[item[key]]) {
result[item[key]] = [];
}
let matched=array.filter(el=>el.UseId===item.UseId);
result[item[key]].push(
{
UseId:item.UseId,
placeName:matched.map(e=>e.placename).join(),
Price:matched.reduce((a, b) => a + b.price, 0)
});//push end
});
You can group your array item based on userid and push placename into array and sum up price for same userid.
const data = [ { userid: 1, placename: 'abc', price: 10 }, { userid: 1, placename: 'pqr', price: 20 }, { userid: 1, placename: 'xyz' , price: 30}, { userid: 2, placename: 'abc' , price: 40}, { userid: 2, placename: 'lmn' , price: 50}],
result = Object.values(data.reduce((r,o) => {
r[o.userid] = r[o.userid] || {userid: o.userid, placename: [], price: 0};
r[o.userid].placename.push(o.placename);
r[o.userid].price += o.price;
return r;
},{}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Whenever you feel like making an empty object and using array.map to populate it, try using .reduce instead
const data = [
{ userid: 1, placename: "abc", price: 10 },
{ userid: 1, placename: "pqr", price: 20 },
{ userid: 1, placename: "xyz" , price: 30},
{ userid: 2, placename: "abc" , price: 40},
{ userid: 2, placename: "lmn" , price: 50}
];
const groupBy = (arr, key) => Object.values(
arr.reduce((acc, item) => {
const k = item[key];
if (!acc[k]) {
acc[k] = {...item};
} else {
acc[k].placename += "," + item.placename;
acc[k].price += item.price;
}
return acc;
}, {})
);
console.log(groupBy(data, "userid"));
I split the solution in 3 steps, X, Y and Z:
X = group the rows by userid;
Y = concat the placenames and sum the prices;
Z = make a simpler object.
let data = [
{ userid: 1, placename: 'abc', price: 10 },
{ userid: 1, placename: 'pqr', price: 20 },
{ userid: 1, placename: 'xyz', price: 30 },
{ userid: 2, placename: 'abc', price: 40 },
];
const X = data.reduce((a, { userid, placename, price }) => { a[userid] = [...(a[userid] || []), { placename, price }]; return a }, {})
const Y = Object.entries(X).map(([userid, items]) => { return [userid, items.reduce((a, c) => { a.placename = [...a.placename.split(',').filter(s => !!s), c.placename].join(','); a.price += c.price; return a; }, { placename: '', price: 0 })]; });
const Z = Y.map(([userid, { placename, price }]) => ({ userid, placename, price }))
console.log(Z)
You can use a single line solution too:
let data = [
{ userid: 1, placename: 'abc', price: 10 },
{ userid: 1, placename: 'pqr', price: 20 },
{ userid: 1, placename: 'xyz', price: 30 },
{ userid: 2, placename: 'abc', price: 40 },
]
const doIt = (data) => Object.entries(data.reduce((a, { userid, placename, price }) => { a[userid] = [...(a[userid] || []), { placename, price }]; return a }, {})).map(([userid, items]) => { return [userid, items.reduce((a, c) => { a.placename = [...a.placename.split(',').filter(s => !!s), c.placename].join(','); a.price += c.price; return a; }, { placename: '', price: 0 })]; }).map(([userid, { placename, price }]) => ({ userid, placename, price }))
console.log(doIt(data))
Have fun!
Related
I have an array of objects, and I'm trying to sum the value of the amount property from each object in the array, based on their address property
I would like to convert something like this:
[
{
amount: 10,
address: a01,
...other props...
},
{
amount: 20,
address: b02,
...other props...
},
{
amount: 5,
address: a01,
...other props...
},
...
]
to:
[
{
address: a01,
totalAmount: 15,
...other props...
},
{
address: b02,
totalAmount: someTotaledAmount,
...other props...
},
...
]
Should I be using reduce to consolidate the objects in the array?
Thank you!
You could definitely use Array.reduce() to sum the amounts by address. We'd create an object with an entry for each value of address.
We can then use Object.values() to get the result as an array.
let input = [ { amount: 10, address: 'a01', otherValue: 'x' }, { amount: 20, address: 'b02', otherValue: 'y' }, { amount: 5, address: 'a01', otherValue: 'z' } ]
const result = Object.values(input.reduce((acc, { amount, address, ...rest }) => {
acc[address] = acc[address] || { address, ...rest, totalAmount: 0 };
acc[address].totalAmount += amount;
return acc;
} , {}));
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; }
You could also use a for ... of loop to do the same thing:
let input = [ { amount: 10, address: 'a01', otherValue: 'x' }, { amount: 20, address: 'b02', otherValue: 'y' }, { amount: 5, address: 'a01', otherValue: 'z' } ]
let result = {};
for(let { amount, address, ...rest} of input) {
if (!result[address]) {
result[address] = { address, ...rest, totalAmount: 0 };
}
result[address].totalAmount += amount;
}
result = Object.values(result);
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; }
How to loop through two arrays of objects and get a new array with some data modified?
Arrays:
const products = [
{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Tees',
},
];
const categories = [
{
name: 'Jeans',
},
{
name: 'Tees',
},
];
Need new categories array like this with new prop productCount:
const newCategories = [
{
name: 'Jeans',
productCount: 2,
},
{
name: 'Tees',
productCount: 0,
},
];
I tried this way but it doesn't work:
const newArr = categories.map((category) => {
let count = 0;
const index = products.findIndex((product) => category.name === product.category);
if (index > -1) {
return {
...category,
productCount: count++,
};
}
return {
...category,
productCount: 0,
};
});
Increasing the count number will not in that case because it will always start with zero. Instead, you can use the filter() method to find the number of products with a specific category and assign this number to productCount attribute.
const products = [{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Tees',
},
];
const categories = [{
name: 'Jeans',
},
{
name: 'Tees',
},
];
const newArr = categories.map((category) => {
const numberOfItems = products.filter((product) => category.name === product.category);
return {
...category,
productCount: numberOfItems.length,
};
});
console.log(newArr)
You can create an object and the transform it to array, something like this:
const products = [
{
brand: "Levis",
category: "Jeans"
},
{
brand: "Levis",
category: "Jeans"
},
{
brand: "Levis",
category: "Tees"
}
];
const categoriesObj = {};
products.forEach(({ brand, category }) => {
categoriesObj[category] ??= {
name: category,
productCount: 0
};
++categoriesObj[category].productCount;
});
const newCategories = Object.values(categoriesObj);
console.log(newCategories);
You can use the Array#Map method and add a productCount property using the Array#filter method
const products = [{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Jeans',
},
{
brand: 'Levis',
category: 'Tees',
},
];
const categories = [{
name: 'Jeans',
},
{
name: 'Tees',
},
];
const newCategories = [...categories].map(category => ({
...category,
productCount: products.filter(product => product.category === category.name).length
}))
console.log(newCategories)
You could do this with Array.reduce(), incrementing the productCount for each item. This should also be efficient, requiring only one iteration of the products array.
We'd run the reduce over both arrays, ensuring that we'll end up with a productCount of zero where no products for that category exist.
const products = [ { brand: 'Levis', category: 'Jeans', }, { brand: 'Levis', category: 'Jeans', }, { brand: 'Levis', category: 'Tees', }, ];
const categories = [ { name: 'Jeans', }, { name: 'Tees', }, { name: 'Foo', } ];
const result = Object.values([...categories, ...products].reduce((acc, { brand, category, name }) => {
const key = name || category;
acc[key] = acc[key] || { name: key, productCount: 0 };
if (category) acc[key].productCount++;
return acc;
}, {}));
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; }
I have a data like this :
const fund =
[
{
id: 1234,
totalAmount: 0,
data:
[
{
id: 1234,
amount: '4.000'
},
{
id: 1234,
amount: '3.000'
}
]
},
{
id: 12345,
totalAmount: 0
},
{
id: 123456,
totalAmount: 0
data:
[
{
id: 123456,
amount: '3.000'
},
{
id: 123456,
amount: '5.000'
}
]
}
]
I want to sum the amount inside of data each id to a key called totalAmount. But not all the parent id have data key.
here's my desired output :
const fund =
[
{
id: 1234
data:
[
{
id: 1234,
amount: '4.000'
},
{
id: 1234,
amount: '3.000'
}
],
totalAmount: 7000
},
{
id: 12345,
totalAmount: 0
},
{
id: 123456,
data:
[
{
id: 123456,
amount: '3.000'
},
{
id: 123456,
amount: '5.000'
}
],
totalAmount: 8000
}
]
I was trying with this code :
fund.forEach((elA, i) => {
if (elA.data) {
const total = funders[i].data.reduce((acc, curr) => {
acc += parseInt(curr.amount.replace(/\./g, ''))
return acc
})
fund[i] = total ? {...elA, totalAmount: total} : elA;
}
})
But it's not summing like i want.
Where's my mistake ?
Please ask me if you need more information if it's still not enough to solve that case.
You need to define the initial value for the reduce iterator.
fund.forEach((elA, i) => {
if (elA.data) {
const total = funders[i].data.reduce((acc, curr) => {
acc += parseInt(curr.amount.replace(/\./g, ''))
return acc
}, 0)
fund[i] = total ? {...elA, totalAmount: total} : elA;
}
});
Another alternative for the same code:
fund.forEach(elA => {
if (elA.data) {
const total = elA.data.reduce((acc, curr) => {
return acc + parseInt(curr.amount.replace(/\./g, ''))
}, 0)
elA.totalAmount = total;
}
});
Maybe someone can give me idea how can i do it. So I have array of names ["Tom", "Jane", "Mike", "John"] and also I have array of objects which are purchase reports:
[
{ date: "19/02/2019", name: "Mike", amount: 10 },
{ date: "20/02/2019", name: "Mike", amount: 15 },
{ date: "21/10/2019", name: "Jane", amount: 25 },
{ date: "22/03/2019", name: "John", amount: 30 },
{ date: "19/03/2019", name: "Tom", amount: 15 }
]
I need to get objects which represent a person and the amount they spend overall. [{ name: "Tom", amount: 15 }, { name: "Mike", amount: 25 }, ... I hope you get the idea. How can I achieve this? I try to map the names and filter array of objects but get undefined.
Use a combination of map, filter and reduce to boil down the data:
The first .map is building the object structure, you want e.g. {name: '...', amount: <val>}
in order to get the value for each of the names, you filter the spending by name and reduce the outputed values by adding them.
const names = ["Tom", "Jane", "Mike", "John"];
const spendings = [{
date: "19/02/2019",
name: "Mike",
amount: 10
}, {
date: "20/02/2019",
name: "Mike",
amount: 15
}, {
date: "21/10/2019",
name: "Jane",
amount: 25
}, {
date: "22/03/2019",
name: "John",
amount: 30
}, {
date: "19/03/2019",
name: "Tom",
amount: 15
}];
const result = names.map(name => {
return {
name,
amount: spendings.filter(spending => spending.name === name).reduce((sum, {
amount
}) => sum + amount, 0)
};
});
console.log(result);
You can try this code:
const data = [
{ date: "19/02/2019", name: "Mike", amount: 10 },
{ date: "20/02/2019", name: "Mike", amount: 15 },
{ date: "21/10/2019", name: "Jane", amount: 25 },
{ date: "22/03/2019", name: "John", amount: 30 },
{ date: "19/03/2019", name: "Tom", amount: 15 },
];
const names = ["Tom", "Jane", "Mike", "John"];
const results = names.map((name) => ({
name,
amount: data
.filter(({ name: dataName }) => dataName === name)
.reduce((total, { amount }) => total + amount, 0),
}));
console.log(results);
.as-console-wrapper { min-height: 100%!important; top: 0; }
You can set it up however you like using the javascript filter method.
Eg. If you want to get entries in the array that match name and amount you can write a function like this:
const result = (name, cost) => array.filter(customer => {
return name == customer.name && cost == customer.cost;
});
Running result("Jane", 25) will return this:
[{date: "21/10/2019", name: "Jane", amount: 25}]
You could create an object with the wanted names and add the amount to each property.
const
names = ["Tom", "Jane", "Mike", "John"],
purchases = [{ date:"19/02/2019", name: "Mike", amount: 10 }, { date: "20/02/2019", name: "Mike", amount: 15 }, { date: "21/10/2019", name: "Jane", amount: 25 }, { date: "22/03/2019", name: "John", amount: 30 }, { date: "19/03/2019", name: "Tom", amount: 15 }],
result = purchases.reduce(
(object, { name, amount }) => (object[name] += amount, object),
Object.fromEntries(names.map(name => [name, 0]))
);
console.log(result);
Use Array.reduce on your report array.
And reduce report array into dictionary of overall report for each name.
try first to solve it by your own.
this is my solution :
const dictionaryReports = reports.reduce((prev,curr)=>{
if(!prev[curr.name]) {
return {...prev,prev[curr.name] : curr}
}else{
return {
...prev,
prev[curr.name]:
{
...prev[curr.name],
amount : prev[curr.name].amount + curr.amount
}
}
}
},{})
the output will be :
dictionaryReports = {
Mike : {name:"Mike",amount:25},
Tom : {name:"Tom",amount:15}
}
then you can do
Object.values(dictionaryReports)
You can use the array reduce method on purchases. You don't need the array of names I think it looks useless for the result.
const purchases = [
{ date: "19/02/2019", name: "Mike", amount: 10 },
{ date: "20/02/2019", name: "Mike", amount: 15 },
{ date: "21/10/2019", name: "Jane", amount: 25 },
{ date: "22/03/2019", name: "John", amount: 30 },
{ date: "19/03/2019", name: "Tom", amount: 15 }
]
const overall = purchases.reduce((acc, curr) => {
const currentUser = acc.find(x => x.name === curr.name);
if(currentUser) {
currentUser.amount += curr.amount;
} else {
acc.push({name:curr.name,amount: curr.amount})
}
return acc;
}, []);
This approach is as generic as can be. It combines a map and a reduce method in a way that any data from a given dataset (list of data) and a corresponding (target) value list can be collected for the latter from the former by just providing a start configuration to the above mentioned map-reduce combination ...
const dataset = [
{ date: "19/03/2019", name: "Jerry", amount: 45 },
{ date: "19/02/2019", name: "Mike", amount: 10 },
{ date: "20/02/2019", name: "Mike", amount: 15 },
{ date: "21/10/2019", name: "Jane", amount: 25 },
{ date: "22/03/2019", name: "John", amount: 30 },
{ date: "19/03/2019", name: "Tom", amount: 15 },
{ date: "19/03/2019", name: "Jerry", amount: 15 }
];
function aggregateTargetItemValueFromSourceKey(collector, item) {
const { aggregateValue, sourceKey, targetKey, targetItem } = collector;
if (targetItem[targetKey] === item[targetKey]) {
targetItem[sourceKey] = aggregateValue(targetItem[sourceKey], item[sourceKey]);
}
return collector;
}
function createTargetItemFromBoundDatasetConfig(targetValue) {
const { dataset, aggregateValue, initialValue, sourceKey, targetKey } = this;
return dataset.reduce(aggregateTargetItemValueFromSourceKey, {
aggregateValue,
sourceKey,
targetKey,
targetItem: {
[targetKey]: targetValue,
[sourceKey]: initialValue
}
}).targetItem;
}
console.log(
["Tom", "Jane", "Mike", "John"]
.map(createTargetItemFromBoundDatasetConfig, {
aggregateValue: ((targetValue, sourceValue) => targetValue + sourceValue),
initialValue: 0,
sourceKey: 'amount',
targetKey: 'name',
dataset
})
);
console.log(
[5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
.map(createTargetItemFromBoundDatasetConfig, {
aggregateValue: ((targetValue, sourceValue) => targetValue.concat(sourceValue)),
initialValue: [],
sourceKey: 'name',
targetKey: 'amount',
dataset
})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The second approach is a straightforward one, thus it is less flexible in the result it does produce. Nevertheless, there is at least the option of plainly aggregating the data from any item in the provided dataset, or, as the OP unintentionally might have hinted by the provided name list, one can use the latter for filtering only those items from the dataset list that are actually feature one of its names. ...
const dataset = [
{ date: "19/03/2019", name: "Jerry", amount: 45 },
{ date: "19/02/2019", name: "Mike", amount: 10 },
{ date: "20/02/2019", name: "Mike", amount: 15 },
{ date: "21/10/2019", name: "Jane", amount: 25 },
{ date: "22/03/2019", name: "John", amount: 30 },
{ date: "19/03/2019", name: "Tom", amount: 15 },
{ date: "19/03/2019", name: "Jerry", amount: 15 }
];
const itemNameList = ["Tom", "Jane", "Mike", "John"];
function aggregateItemAmountByItemNameWithOptionalNameCeck(collector, item) {
const { checklist, index, list } = collector;
const itemName = item.name;
const isProceed = (!Array.isArray(checklist) || checklist.includes(itemName))
if (isProceed) {
let targetItem = index[itemName];
if (!targetItem) {
targetItem = index[itemName] = {
name: itemName,
amount: 0
};
list.push(targetItem);
}
targetItem.amount = targetItem.amount + item.amount;
}
return collector;
}
console.log(
'with name check ... ',
dataset.reduce(aggregateItemAmountByItemNameWithOptionalNameCeck, {
checklist: itemNameList,
index: {},
list: []
}).list
);
console.log(
'without name check ... ',
dataset.reduce(aggregateItemAmountByItemNameWithOptionalNameCeck, {
index: {},
list: []
}).list
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
As I'm trying to get an object with all the names with the highest values, I am only getting one value and name rather than all existing names with the same highest value? // { name: 'Stephen', total: 85 }
Any help would be appreciated.
const students = [
{ name: 'Andy', total: 40 },
{ name: 'Seric', total: 50 },
{ name: 'Stephen', total: 85 },
{ name: 'David', total: 30 },
{ name: 'Phil', total: 40 },
{ name: 'Eric', total: 85 },
{ name: 'Cameron', total: 30 },
{ name: 'Geoff', total: 30 }];
const max = Math.max(...students.map(e => e.total))
const result = students.find(student => student.total === max)
console.log(result)//{ name: 'Stephen', total: 85 }
Use
const result = students.filter(student => student.total == max)
Another solution using a single forEach loop, which returns an array of the top students.
const students = [{ name: 'Andy', total: 40 },{ name: 'Seric', total: 50 },{ name: 'Stephen', total: 85 },{ name: 'David', total: 30 },{ name: 'Phil', total: 40 },{ name: 'Eric', total: 85 },{ name: 'Cameron', total: 30 },{ name: 'Geoff', total: 30 }];
const findTop = (students) => {
let max = 0;
let top = [];
students.forEach(student => {
if (student.total > max) {
max = student.total;
top = [student];
} else if (student.total === max) {
top.push(student);
}
})
return top;
};
console.log(findTop(students));
One pass over the array is absolutely enough to do the job with Array.prototype.reduce():
const students=[{name:'Andy',total:40},{name:'Seric',total:50},{name:'Stephen',total:85},{name:'David',total:30},{name:'Phil',total:40},{name:'Eric',total:85},{name:'Cameron',total:30},{name:'Geoff',total:30}],
result = students.reduce((res,student,idx) => (
!idx || student.total > res[0]['total'] ?
res = [student] :
student.total == res[0]['total'] ?
res.push(student) :
true
, res),[])
console.log(result)
.as-console-wrapper {min-height:100%}
You could take a single loop with reduce by returning the object with the greatest total.
const
students = [{ name: 'Andy', total: 40 }, { name: 'Seric', total: 50 }, { name: 'Stephen', total: 85 }, { name: 'David', total: 30 }, { name: 'Phil', total: 40 }, { name: 'Eric', total: 85 }, { name: 'Cameron', total: 30 }, { name: 'Geoff', total: 30 }]
highest = students.reduce((r, o) => {
if (!r || r[0].total < o.total) return [o];
if (r[0].total === o.total) r.push(o);
return r;
}, undefined);
console.log(highest);