Reputation: 8033
I have an array of objects like this
const inputArray = [
{ name: "sam", date: "1 / 1 / 23", confirmed: "yes", spent: 0 },
{ name: "sam", date: "1 / 2 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 3 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 4 / 23", confirmed: "no", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 6 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
];
I want an output object like this
spent
is just an addition of all spent
for a name
confirmedAndNotSpent
is just the addition of all confirmed
as 'yes' but spent
as 0 for a name
notConfirmedAndSpent
is just an addition of all confirmed
as 'no' for a name
const outputObj = {
sam: { spent: 12, confirmedAndNotSpent: 1, notConfirmedAndSpent: 1 },
bill: { spent: 8, confirmedAndNotSpent: 1, notConfirmedAndSpent: 0 },
bill: { spent: 4, confirmedAndNotSpent: 1, notConfirmedAndSpent: 2 },
};
How do I achieve this.
I tired this first
let try1 = inputArray.reduce((accumulator, current) => {
if (!accumulator[current.name]) accumulator[current.name] = 0;
accumulator[current.name] += +current.spent;
return accumulator;
}, {});
This gives the the output like so { sam: 12, bill: 8, annie: 4 }
.
However, I am unable to convert even this into an object with the code below.
let try2 = inputArray.reduce((accumulator, current) => {
if (!accumulator[current.name]) accumulator[current.name] = {};
accumulator[current.name][current.spent] += +current.spent;
return accumulator;
}, {})
The above code gives the out put below
{
sam: { '0': NaN, '4': NaN },
bill: { '0': NaN, '4': NaN },
annie: { '0': NaN, '2': NaN }
}
Can anyone help to get the output I want? Thanks.
Update: Output in a different format Just one follow-up, how to get the output like below?
const outputObj2 = [
{ name: "sam", spent: 12, confirmedAndNotSpent: 1, notConfirmedAndSpent: 1 },
{ name: "bill", spent: 8, confirmedAndNotSpent: 1, notConfirmedAndSpent: 0 },
{ name: "annie", spent: 4, confirmedAndNotSpent: 1, notConfirmedAndSpent: 2 },
];
The code below gets me the output I want but is this best way?.
let try3 = inputArray.reduce((accumulator, current) => {
if (!accumulator.some((accumulator) => accumulator.name === current.name)) {
let obj = {
name: current.name,
spent: current.spent,
confirmedAndNotSpent:
current.confirmed === "yes" && current.spent === 0 ? 1 : 0,
notConfirmedAndSpent: current.confirmed === "no" ? 1 : 0,
};
accumulator.push(obj);
} else {
//Find index of specific object using findIndex method.
objIndex = accumulator.findIndex((obj) => obj.name == current.name);
//update the specific object using index
accumulator[objIndex].spent += current.spent;
if (current.confirmed === "yes" && current.spent === 0) {
accumulator[objIndex].confirmedAndNotSpent++;
}
if (current.confirmed === "no") {
accumulator[objIndex].notConfirmedAndSpent++;
}
}
return accumulator;
}, []);
The output I get is as below. Only the details of the last person is coming & it is not even an object. I think I am missing the portion of pushing details of each name as an object into the array but for the life of me, I am not able to figure it out.
[
{
name: 'sam',
spent: 12,
confirmedAndNotSpent: 1,
notConfirmedAndSpent: 1
},
{
name: 'bill',
spent: 8,
confirmedAndNotSpent: 1,
notConfirmedAndSpent: 0
},
{
name: 'annie',
spent: 4,
confirmedAndNotSpent: 1,
notConfirmedAndSpent: 2
}
]
Please add the answer to the existing portion of the answer.
Upvotes: 1
Views: 97
Reputation: 1716
Two issues with your code:
try2
, this line:accumulator[current.name][current.spent] += +current.spent;
should be:
accumulator[current.name].spent += +current.spent;
undefined + 0
(or any number) is equal to NaN. To solve this. You have to first initialize your spent
property to 0: if (!accumulator[current.name]) accumulator[current.name] = { spent: 0 }
For the other variables, these can all be accomplished in a similar manner, just with the necessary conditional checks.
const inputArray = [
{ name: "sam", date: "1 / 1 / 23", confirmed: "yes", spent: 0 },
{ name: "sam", date: "1 / 2 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 3 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 4 / 23", confirmed: "no", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 6 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
];
let try2 = inputArray.reduce((accumulator, current) => {
if (!accumulator[current.name]) accumulator[current.name] = {
spent: 0,
confirmedAndNotSpent: 0,
notConfirmedAndSpent: 0
};
accumulator[current.name].spent += current.spent;
if(current.confirmed === "yes" && current.spent === 0) {
accumulator[current.name].confirmedAndNotSpent++;
}
if(current.confirmed === "no") {
accumulator[current.name].notConfirmedAndSpent++;
}
return accumulator;
}, {})
document.getElementById("out").innerText = JSON.stringify(try2, null, 4)
<pre id="out"></p>
EDIT:
To get these in an array instead, the code can remain largely the same, however you do need to find the object within the accumulator array that corresponds to the current person.
const inputArray = [
{ name: "sam", date: "1 / 1 / 23", confirmed: "yes", spent: 0 },
{ name: "sam", date: "1 / 2 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 3 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 4 / 23", confirmed: "no", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 6 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
];
let try2 = inputArray.reduce((accumulator, current) => {
// Find the object within the array for the current person.
let personObj = accumulator.find((v) => v.name === current.name);
// If there is no object for the current person within the array, create it and push it.
if(!personObj) {
personObj = {
name: current.name,
spent: 0,
confirmedAndNotSpent: 0,
notConfirmedAndSpent: 0
};
accumulator.push(personObj);
}
// Same as above, just on the person object...
personObj.spent += current.spent;
if(current.confirmed === "yes" && current.spent === 0) {
personObj.confirmedAndNotSpent++;
}
if(current.confirmed === "no") {
personObj.notConfirmedAndSpent++;
}
return accumulator;
}, [])
document.getElementById("out").innerText = JSON.stringify(try2, null, 4)
<pre id="out"></p>
This implementation won't scale well (complexity O(n^2)), but can be improved by combining the two techniques to create your array after creating the map (O(n)).
const inputArray = [
{ name: "sam", date: "1 / 1 / 23", confirmed: "yes", spent: 0 },
{ name: "sam", date: "1 / 2 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 3 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 4 / 23", confirmed: "no", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 6 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
];
// All same as original answer...
let try2 = inputArray.reduce((accumulator, current) => {
if (!accumulator[current.name]) accumulator[current.name] = {
name: current.name,
spent: 0,
confirmedAndNotSpent: 0,
notConfirmedAndSpent: 0
};
accumulator[current.name].spent += current.spent;
if(current.confirmed === "yes" && current.spent === 0) {
accumulator[current.name].confirmedAndNotSpent++;
}
if(current.confirmed === "no") {
accumulator[current.name].notConfirmedAndSpent++;
}
return accumulator;
}, {})
// Now convert the object into an array by pushing each key's value to an array.
const try2AsArr = [];
for(const key of Object.keys(try2)) {
try2AsArr.push(try2[key]);
}
document.getElementById("out").innerText = JSON.stringify(try2AsArr, null, 4)
<pre id="out"></p>
Upvotes: 1
Reputation: 1090
const inputArray = [
{ name: "sam", date: "1 / 1 / 23", confirmed: "yes", spent: 0 },
{ name: "sam", date: "1 / 2 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 3 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 4 / 23", confirmed: "no", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 6 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
];
const output = inputArray.reduce((acc, {name, confirmed, spent: currSpent}) => {
let {spent, confirmedAndNotSpent, notConfirmedAndSpent} = acc[name] || {spent: 0, confirmedAndNotSpent: 0, notConfirmedAndSpent: 0}
spent += currSpent
confirmedAndNotSpent += confirmed === 'yes' && currSpent === 0
notConfirmedAndSpent += confirmed === 'no'
return {...acc, [name]: {spent, confirmedAndNotSpent, notConfirmedAndSpent, }}
}, {})
console.log(output)
produces:
{
annie: {
confirmedAndNotSpent: 1,
notConfirmedAndSpent: 2,
spent: 4
},
bill: {
confirmedAndNotSpent: 1,
notConfirmedAndSpent: 0,
spent: 8
},
sam: {
confirmedAndNotSpent: 1,
notConfirmedAndSpent: 1,
spent: 12
}
}
The idea is to reduce the input array to the desired object as you started to do, but accumulate entire objects for each name, rather than just individual fields of the goal objects, and to create the per-name-default objects with the desired keys and appropriate initial values.
Upvotes: 1
Reputation: 370789
When iterating over an array item, perform all the checks you need in that one iteration - increment spent
, increment confirmedAndNotSpent
if appropriate, and increment notConfirmedAndSpent
if appropriate. Make the accumulator (or outputObj
) an object of objects - the value should be an object, not just a number, to hold all the information you want.
const inputArray = [
{ name: "sam", date: "1 / 1 / 23", confirmed: "yes", spent: 0 },
{ name: "sam", date: "1 / 2 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 3 / 23", confirmed: "yes", spent: 4 },
{ name: "sam", date: "1 / 4 / 23", confirmed: "no", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 6 / 23", confirmed: "yes", spent: 4 },
{ name: "bill", date: "1 / 5 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "yes", spent: 0 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
{ name: "annie", date: "1 / 6 / 23", confirmed: "no", spent: 2 },
];
const output = {};
for (const { name, date, confirmed, spent } of inputArray) {
output[name] ??= { spent: 0, confirmedAndNotSpent: 0, notConfirmedAndSpent: 0 };
output[name].spent += spent;
if (confirmed === 'yes' && spent === 0) {
output[name].confirmedAndNotSpent++;
}
if (confirmed === 'no') {
output[name].notConfirmedAndSpent++;
}
}
console.log(output);
Upvotes: 1