Reduce method on array of objects with conditional - javascript

I have an array of objects, data, that I'm attempting to retrieve the total of both shoe sizes.
I'm having issues counting "size 10" apart from "size 5". I've attempted to use the .reduce() method with a conditional to only add the total count if the names match:
const data = [
{ "shoeSize": "size 10", "total": 9 },
{ "shoeSize": "size 10", "total": 3 },
{ "shoeSize": "size 5", "total": 2 }
];
const grandTotal = data.reduce((prev, curr) => {
return {
shoeSize10Total:
prev.shoeSize === "size 10" ||
curr.shoeSize === "size 10" &&
prev.total + curr.total,
shoeSize5Total:
prev.shoeSize === "size 5" ||
curr.shoeSize === "size 5" &&
prev.total + curr.total
};
});
console.log(grandTotal);
However, the result of this method returns NaN and false - instead of the total amount of numbers for each.
I've tried looking up a solution and found something similar on this post, but they are filtering to return the value of 1; I would like to return both in an object.
This is the intended result of my .reduce():
const grandTotal = {
shoeSize10Total: 12
shoeSize5Total: 2
};

I think using .reduce here makes things too convoluted. Consider a plain loop where you increment the appropriate property an an object declared outside.
const data = [
{ shoeSize: "size 10", total: 9},
{ shoeSize: "size 10", total: 3},
{ shoeSize: "size 5", total: 2}
];
const grandTotal = {};
for (const { shoeSize, total } of data) {
const digits = shoeSize.match(/\d+/)[0];
if (!digits) continue;
const key = `shoeSize${digits}Total`;
grandTotal[key] = (grandTotal[key] || 0) + total;
}
console.log(grandTotal);

We could implement basic approaches using EcmaScript Methods such as filter. Take a look at this code snippet, pal. I certainly hope this helps, buddy!
const data = [
{ shoeSize: "size 10", total: 9 },
{ shoeSize: "size 10", total: 3 },
{ shoeSize: "size 5", total: 2 },
];
let shoeSize10Total = 0;
let shoeSize05Total = 0;
const grandTotal = (data) => {
data.filter((el) => {
if (el.shoeSize === "size 10") {
shoeSize10Total += el.total;
} else {
shoeSize05Total += el.total;
}
});
return { shoeSize10Total, shoeSize05Total };
};
console.log(grandTotal(data));

One of issues is that you are not using the initialValue of reduce method.
Moreover, the approach to build the grandTotal is far from the best as the values are hardcoded into the reduce handler, for multiple size values that code will be a mess.
A cleaner approach is to create a mapping of input values to output keys, then in the reduce handler just to use that mapping for lookups, like this:
const data = [
{ shoeSize: "size 10", total: 9},
{ shoeSize: "size 10", total: 3},
{ shoeSize: "size 5", total: 2}
];
const map = {
'size 5': 'shoeSize5Total',
'size 10': 'shoeSize10Total'
};
const grandTotal = data.reduce((o, {shoeSize: s, total}) =>
(o[map[s]] = (o[map[s]] ?? 0) + total, o), {});
console.log(grandTotal);

little info about below code
firstly use {} object as initial value to reduce function
now extract shoeSize from data array and use it as a key for new object
now write a condition if that key exist in new object then increment the value otherwise create a new property to object
boom at the end you will have a sorted object as a result
const data = [
{ shoeSize: "size 10", total: 9 },
{ shoeSize: "size 10", total: 3 },
{ shoeSize: "size 5", total: 2 }
]
let res = data.reduce((main,each)=>{
let num = each.shoeSize.match(/\d+/)[0],
newKey = `shoeSize${num}Total`,
exist = typeof main[newKey] !== "undefined"
if(exist){main[newKey] += each.total}
else{main[newKey] = each.total}
return main
},{})
console.log(res)

A simple forEach will do:
const data = [
{ shoeSize: "size 10", total: 9},
{ shoeSize: "size 10", total: 3},
{ shoeSize: "size 5", total: 2}
]
const n = {}
data.forEach(d => {
const key = "Shoe" + d.shoeSize.charAt(0).toUpperCase() + d.shoeSize.slice(1).replace(" ","") + 'Total';
const res = Object.keys(n).filter(_i => _i == key)
if (res.length == 0) {
n[key] = 0;
}
n[key] += d.total;
})
console.log(n)

I don't think .reduce is the right think to use in this problem because it makes things a little bit more complex in this situation. So I prefer always using functions to do the work for me.
function collectShoesTogether(arr) {
var obj = {};
arr.forEach((item) => {
let key = `shoeSize${item.shoeSize.split(" ").join("")}Total`;
if (obj[key] === undefined) {
obj[key] = item.total;
} else {
obj[key] += item.total;
}
});
return obj;
}

Related

Javascript - How to sum the values in such an array?

I have such an array:
let array = {
[1]: {
name: 'test 1',
count: 5
},
[2]: {
name: 'test 2',
count: 3
}
}
How can I sum the values in the "count" column? Examples from simple arrays do not work. I currently have such a loop. Can it be done somehow better?
let sum = 0
Object.entries(array).forEach(([key, val]) => {
sum += val.count
});
Use reduce
let array = { 1: { name: "test 1", count: 5, }, 2: { name: "test 2", count: 3, }, };
total = Object.values(array).reduce((t, { count }) => t + count, 0); //t accumulator accumulates the value from previous calculation
console.log(total);
if you want to use a forEach loop like in your method use Object.values() instead because you only need values to calculate the sum of count
let array = {
1: { name: "test 1", count: 5 },
2: { name: "test 2", count: 3 },
};
let sum = 0;
Object.values(array).forEach(({ count }) => {
sum += count;
});
console.log(sum);
Building on top of the answer provided by #Sven.hig
Since you are calling the object "array" you might want to use an actual array instead.
Creating some functions to abstract away the complexity will help you understand your code better, when you come back to it in the future.
const add = (a, b) => a + b;
const sum = arr => arr.reduce(add, 0);
const data = [{
name: "test 1",
count: 5,
}, {
name: "test 2",
count: 3,
}
];
const total = sum(
data.map(d => d.count)
);
console.log(total);

Categorisation of objects by comparing two objects in javascript

I am trying to categorise the objects by comparing two objects say data and categories
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category":["A","1a", "2a"],
"B_category":["1b", "2b"],
"C_category":["1c", "2c"],
"D_category":["1d", "2d"]
};
I want to group the data based on the category object, when there is no match the group should be others and the resultant data should be like
const resultData = [
{ group: 'Others', name: '777', count: 456 },
{ group: 'A_category', name: '1a', count: 154 },
{ group: 'B_category', name: '1b', count: 765 },
{ group: 'C_category', name: '1c', count: 7877 }
]
I used the function but not able to achieve the result
const resultData = [];
function restructure(data, categories) {
Object.keys(data).map(
dataKey => {
for (let [key, value] of Object.entries(categories)) {
value.includes(dataKey) ? resultData.push({"group": key,...data[dataKey]}) : resultData.push({"group": "Others",...data[dataKey]}) ;
break;
}
}
)
}
restructure(data,categories);
You can try this as well. Iterate over your data entries and find whether the key exists in any of the categories object data and push it into the array with found category as group or push it with Others as group as shown in the below code
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category": ["A", "1a", "2a"],
"B_category": ["1b", "2b"],
"C_category": ["1c", "2c"],
"D_category": ["1d", "2d"]
};
const resultData = [];
Object.entries(data).map(([key, val])=>{
let group = Object.keys(categories).find(category=>categories[category].includes(key)) || 'Others'
resultData.push({
group,
...val
})
})
console.log(resultData)
Instead of for loop you need to use filter as let category = Object.entries(categories).filter(([key, value]) => value.includes(dataKey));.
If category.length > 0 then category is available else use Others.
Try it below.
const data = {
"1a": {
"name": "1a",
"count": 154
},
"1b": {
"name": "1b",
"count": 765
},
"1c": {
"name": "1c",
"count": 7877
},
"777": {
"name": "777",
"count": 456
}
};
const categories = {
"A_category": ["A", "1a", "2a"],
"B_category": ["1b", "2b"],
"C_category": ["1c", "2c"],
"D_category": ["1d", "2d"]
};
const resultData = [];
function restructure(data, categories) {
Object.keys(data).map(
dataKey => {
let category = Object.entries(categories)
.filter(([key, value]) => value.includes(dataKey));
resultData.push({
"group": category.length > 0 ? category[0][0] : "Others",
...data[dataKey]
});
})
}
restructure(data, categories);
console.log(resultData);
That's because you're breaking out of the loop regardless of whether you found the category or not. Your for loop will only execute once then breaks immediately. If the first category object matches, it is used, if not "Others" is assigned and the loop exits without checking the rest of the categories. Only break out of the loop if the lookup is successful:
for (let [key, value] of Object.entries(categories)) {
if(value.includes(dataKey)) { // if this is the category
resultData.push({ "group": key, ...data[dataKey] }); // use it ...
return; // ... and break the loop and the current iteration of forEach. The current object is handled
}
}
resultData.push({ "group": "Others", ...data[dataKey] }); // if the return statement above is never reached, that means the category was not found, assign "Others"
BTW, you can use other array methods to shorten things out like so:
function restructure(data, categories) {
return Object.keys(data).map(key => ({
"group": Object.keys(categories).find(cat => categories[cat].includes(key)) || "Others",
...data[key]
}));
}
Then use like so:
const resultData = restructure(data, categories);
My method uses find to try to find a category key that contains the name of the object, if find fails, it returns null at which point, the || "Others" part is evaluated and "Others" will be used as the group name (Does JavaScript have "Short-circuit" evaluation?).
Demo:
const data = {"777":{"name":"777","count":456},"1a":{"name":"1a","count":154},"1b":{"name":"1b","count":765},"1c":{"name":"1c","count":7877}};
const categories = {"A_category":["A","1a","2a"],"B_category":["1b","2b"],"C_category":["1c","2c"],"D_category":["1d","2d"]};
function restructure(data, categories) {
return Object.keys(data).map(key => ({
"group": Object.keys(categories).find(cat => categories[cat].includes(key)) || "Others",
...data[key]
}));
}
const resultData = restructure(data, categories);
console.log(resultData);

How to calculate the sum of items in an array?

Suppose i have an array:
const items = [
{
"amount1": "100",
"amount2": "50",
"name": "ruud"
},
{
"amount1": "40",
"amount2": "60",
"name": "ted"
}
]
I want to get all amount1 and amount2 props totalled and result in:
[
{
"amount1": 140,
"amount2": 110
}
]
How can I do this?
Using Array.prototype.reduce() with Object.entries() and Array.prototype.forEach():
const items = [{amount1: 100, amount2: 50}, {amount1: 40, amount2: 60}];
const sums = items.reduce((acc, item) => {
Object.entries(item).forEach(([k, v]) => acc[k] = (acc[k] || 0) + v);
return acc;
}, {});
console.log(sums);
To filter out non-number properties (but keep quoted number strings, as per the updated question):
const items = [{amount1: '100', amount2: '50', name: 'Ruud'}, {amount1: '40', amount2: '60', name: 'Ted'}];
const sums = items.reduce((acc, item) => {
Object.entries(item)
.filter(([_, v]) => !isNaN(v))
.forEach(([k, v]) => acc[k] = (acc[k] || 0) + Number(v));
return acc;
}, {});
console.log(sums);
const items = [{amount1: 100, amount2: 50}, {amount1: 40, amount2: 60}];
function sum(data){
const keys = Object.keys(data[0])
let res = {}
for(key of keys)
res[key]=data.map(x=>x[key]).reduce((a,b)=>a+b);
return res
}
console.log(sum(items))
Here is an alternative, simple, and clean solution for this.
const items = [{amount1:100, amount2:50, name:"ruud"},{amount1:40,amount2:60,name:"ted"}]
let result = [{amount1:0,amount2:0}]
items.forEach(i=>{
result[0].amount1 += i.amount1
result[0].amount2 += i.amount2
})
console.log(result)
Above solutions are great. I included this if you don't want to use
Array.prototype.reduce(). This will work even if you have other properties which are not "numbers"
const items = [{amount1: 100, amount2: 50, name: 'Ruud'}, {amount1: 40, amount2: 60, name: 'Ted'}];
var result = {};
items.forEach(function(eachItem){
for(var prop in eachItem){
if(typeof eachItem[prop] === "number"){
result[prop] = result[prop] ? result[prop] + eachItem[prop] : eachItem[prop];
}
}
});
result = [result];
console.log(result);
You can use reduce().
Use the reduce() method on the array items
Set the accumulator(ac) as an empty object i.e {}
During each iteration through the objects create a for..in loop to iterate through all keys of object.
Check if the typeof value of key is "number" then add it otherwise don't
const items = [{amount1:100, amount2:50, name:"ruud"}, {amount1:40,amount2:60,name:"ted"}]
let res = [items.reduce((ac,x) => {
for(let key in x){
if(typeof x[key] === "number"){
if(!ac[key]) ac[key] = 0;
ac[key] += x[key]
}
}
return ac;
},{})]
console.log(res)
reduce() is indeed the way to go, but the cleanest to go only through a set of known keys is probably to pass your expected result as the accumulator and to iterate over this accumulator's keys:
const items = [
{ amount1: "100", amount2: "50", name: "ruud", foo: "unrelated" },
{ amount1: "40", amount2: "60", name: "ted", foo: "0" }
];
const result = items.reduce((acc, item) => {
for (let key in acc) { // iterate over the accumulator's keys
acc[key] += isNaN(item[key]) ? 0 : +item[key];
}
return acc;
}, { // here we define the expected format
amount1: 0,
amount2: 0
});
console.log(result);

Add together an array of objects

I am trying to add together an array of objects, using reduce however i can't get it work.
const testArray = [
{
"T1": 1
},
{
"T2": 12
},
{
"T3": 20
}
]
reduce function
const q = testArray.reduce((count, x) => count + x.P1Count);
outcome = 33
You could get the values and reduce the values as well.
const
add = (a, b) => a + b,
array = [{ "T1": 1 }, { "T2": 12 }, { "T3": 20 }],
total = array.reduce(
(s, o) => Object.values(o).reduce(add, s),
0
);
console.log(total);
The 2nd argument of the reduce() function will be the member of the array the reduce is being called on. In your case, which will be { T[i]: ... } where i = 1, 2, 3.
You can try this:
const testArray = [
{
"T1": 1
},
{
"T2": 12
},
{
"T3": 20
}
]
const x = testArray.reduce((count, x, index) => {
const key = `T${index+1}`; // prepare the dynamic key T1, T2,...
return count + x[key];
}, 0); // <-- 0 is the initial value of sum
console.log(x)

Change the structure of my array [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I'd like to change the structure of my result.There are objects "itemGroup" and I'd like to delete them and keep keys "fruit" and "vegetable".
[{"id": 1, "shop": "shop1", "itemGroup": {"fruit": 2, "vegetable": 2},"total":4},
{"id": 2, "shop": "shop2", "itemGroup": {"fruit": 0, "vegetable": 1},"total":1}]
I'd like this result
[
{ "id": 1, "shop": "shop1", "fruit": 2, "vegetable": 2, "total": 4 },
{ "id": 2, "shop": "shop2" "fruit": 0, "vegetable": 1, "total": 1 }
]
my code
var myArray = [
{shop: "shop1",item1: "my apple 1",item2: "my carrot 1"},
{shop: "shop1",item1: "my apple 1",item2: "my carrot 1"},
{shop: "shop2",item1: "my apple 0",item2: "my carrot 0"},
{shop: "shop2",item1: "my apple 0",item2: "my carrot 1"}
];
var MyArrayDefinition = [
{item: "my apple 0",color: "red", group: "fruit",score: 0},
{item: "my carrot 1",color: "orange",group: "vegetable",score: 1},
{item: "my apple 1",color: "red", group: "fruit",score: 1},
{item: "my carrot 0",color: "orange",group: "vegetable",score: 0}
];
var k = Object.keys,
items = MyArrayDefinition.reduce((o, v) => (o[v.item] = v, o), {});
var shops = myArray.reduce((o, v, i, s) => (
s = v[k(v).find(k => k)],
s = o[s] || (o[s] = {
fruit: 0,
vegetable: 0,
}),
k(v).forEach(k => k.includes('item') &&
(s[(i = items[v[k]]).group] += i.score)), o), {});
var result = k(shops).map((k, i) => ({
id: i + 1,
shop: k,
itemGroup: shops[k],
total:Object.values(shops[k]).reduce((a, b) => a + b),
}));
Pretty much like in most of your questions from the last couple of days. :-)
You can map over the data, use Object.assign and delete the itemGroup.
let x = f.map(e => {
e = Object.assign(e, e.itemGroup);
delete e.itemGroup;
return e;
})
console.log(x);
<script>
let f = [{
"id": 1,
"shop": "shop1",
"itemGroup": {
"fruit": 2,
"vegetable": 2
},
"total": 4
},
{
"id": 2,
"shop": "shop2",
"itemGroup": {
"fruit": 0,
"vegetable": 1
},
"total": 1
}
]
</script>
Expounding on your "original question" set the percentages of each item in the store, and answering your "modified question" change the structure of my array, this gives you both by modifying your original code.
let myArray = [{"shop":"shop1","item1":"my apple 1","item2":"my carrot 1"},{"shop":"shop1","item1":"my apple 1","item2":"my carrot 1"},{"shop":"shop2","item1":"my apple 0","item2":"my carrot 0"},{"shop":"shop2","item1":"my apple 0","item2":"my carrot 1"}]
let MyArrayDefinition = [{"item":"my apple 0","color":"red","group":"fruit","score":0},{"item":"my carrot 1","color":"orange","group":"vegetable","score": null},{"item":"my apple 1","color":"red","group":"fruit","score":1},{"item":"my carrot 0","color":"orange","group":"vegetable","score":0}]
let k = Object.keys
let items = MyArrayDefinition.reduce((o, v) => (o[v.item] = v, o), {})
let shops = myArray.reduce(function (o, v, i, s) {
return s = v[k(v).find(function (k) {
return k;
})], s = o[s] || (o[s] = {
fruit: 0,
vegetable: 0
}), k(v).forEach(function (k) {
return k.includes('item') && (s[(i = items[v[k]]).group] += i.score);
}), o;
}, {});
// Helper function that calculates percentage
function percentage (amount, total) {
if (total === 0) { // added check for 0 divisor
return `0%`
}
return `${(amount / total) * 100}%`
}
let result = k(shops).map((k, i) => {
let total = Object.values(shops[k]).reduce((a, b) => a + b) | 0 // added check if number else 0
let fruit = shops[k].fruit | 0 // added check if number else 0
let veg = shops[k].vegetable | 0 // added check if number else 0
return {
id: i + 1,
shop: k,
fruit: fruit,
vegetable: veg,
total: total,
fruitPercentage: percentage(fruit, total),
vegetablePercentage: percentage(veg, total)
}
})
console.log(JSON.stringify(result, null, 2))
/** result from console.log()
*
[
{
"id": 1,
"shop": "shop1",
"fruit": 2,
"vegetable": 2,
"total": 4,
"fruitPercentage": "50%",
"vegetablePercentage": "50%"
},
{
"id": 2,
"shop": "shop2",
"fruit": 2,
"vegetable": 0,
"total": 2,
"fruitPercentage": "100%",
"vegetablePercentage": "0%"
}
]
* */
Using map is the way to transpose the data from one array onto another and run calculations if needed.
// Create a function that takes in your result array
function setPercentage (array) {
// Helper function that calculates percentage
function percentage (amount, total) {
return (amount / total) * 100
}
// Map the results of the input array onto a new array,
// and return the result
return array.map((obj) => {
return {
id: obj.id,
shop: obj.shop,
fruit: percentage(obj.itemGroup.fruit, obj.total),
vegetable: percentage(obj.itemGroup.vegetable, obj.total),
total: obj.total
}
})
}
// pass in the result array from your code...
const shops_by_percentage = setPercentage(result)
console.log(shops_by_percentage)
/** result in the console.log()
*
[
{
'id': 1,
'shop': 'shop1',
'fruit': 50,
'vegetable': 50,
'total': 4
},
{
'id': 2,
'shop': 'shop2',
'fruit': 0,
'vegetable': 100,
'total': 1
}
]
*
* */
Below you can find general solution to your problem.
Using this approach you can create unlimited number of items in your items array as well as unlimited number of groups in your definitions and your code will still work as expected. Lastly your score value acts as weight (when you give some item for example score 2 each occurrence will count as two items).
// Your items
const items = [
{
shop: "shop1",
item1: "my apple 1",
item2: "my carrot 1",
},
{
shop: "shop1",
item1: "my apple 1",
item2: "my carrot 1"
},
{
shop: "shop2",
item1: "my apple 0",
item2: "my carrot 0"
},
{
shop: "shop2",
item1: "my apple 0",
item2: "my carrot 1"
},
];
// Your definitions
const definitions = [
{
item: "my apple 0",
color: "red",
group: "fruit",
score: 0
},
{
item: "my carrot 1",
color: "orange",
group: "vegetable",
score: 1
},
{
item: "my apple 1",
color: "red",
group: "fruit",
score: 1
},
{
item: "my carrot 0",
color: "orange",
group: "vegetable",
score: 0
}
];
function groupShops(items) {
return items.reduce((acc, cur) => {
// Find shop with id of current item in accumulator
const currentShop = acc.find(shop => shop.id === cur.shop);
// Get all shop items
const shopItems = Object.keys(cur)
// Filter shop key as it is shop's ID
.filter(key => key !== 'shop')
// Map keys into values
.map(key => cur[key]);
// If shop already exists in accumulator
if (!!currentShop) {
// Return updated accumulator
return acc
// Remove current shop
.filter(shop => shop !== currentShop)
// And add new copy of current shop with new items to the accumulator
.concat({
id: currentShop.id,
items: currentShop.items.concat(shopItems),
});
}
// If shop doesn't exist in accumulator add it there and return updated accumulator
return acc.concat({
id: cur.shop,
items: shopItems,
});
}, []);
};
function extendItems(shops) {
// Filter items which have score 0 or less
const filterItems = items => items.filter(item => item.score > 0);
// Map though shops
return shops.map(shop => {
// Return updated shop
return {
// Keep shop id
id: shop.id,
// Extend itemIds by the properties stored in the definition and filter them
items: filterItems(shop.items.map(item => definitions.find(definition => definition.item === item))),
}
});
}
function calculateResult(shop, index) {
// Get all available groups
const availableGroups = definitions.reduce((acc, cur) => acc.indexOf(cur.group) > -1 ? acc : acc.concat(cur.group), []);
// Calculate total possible score
const getTotalScore = () => shop.items.reduce((acc, cur) => cur.score + acc, 0);
// Get score of a passed group
const getGroupScore = group => shop.items.reduce((acc, cur) => cur.group === group ? acc + cur.score : acc, 0);
// Loop though each available group and get its score
const resultData = availableGroups.reduce((acc, cur) => {
return {
// Copy data from accumulator
...acc,
// Add new property to the accumulator with a property key {group name} and value {percantage}
[cur]: getGroupScore(cur, shop.items) / getTotalScore(shop.items) * 100,
}
}, {});
// Return result object
return {
// Destruct items of the result object
...resultData,
// Store total items count
total: shop.items.length,
// Store shop id
shop: shop.id,
// Store index
id: index,
}
}
// Groups shops
const groupedShops = groupShops(items);
// Groups shops with extended items
const extendedShops = extendItems(groupedShops);
// You result object
const result = extendedShops.map((shop, index) => calculateResult(shop, ++index));
console.log(result);

Categories

Resources