Home > Software engineering >  Reduce an arry of objects to an object with key value pairs with addition & comparision
Reduce an arry of objects to an object with key value pairs with addition & comparision

Time:02-05

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

  • value of key spent is just an addition of all spent for a name
  • value of key confirmedAndNotSpent is just the addition of all confirmed as 'yes' but spent as 0 for a name
  • value of key 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.

CodePudding user response:

Two issues with your code:

  1. You made a typo. Where you declare try2, this line:
accumulator[current.name][current.spent]  =  current.spent;

should be:

accumulator[current.name].spent  =  current.spent;
  1. In JavaScript, 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>

CodePudding user response:

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);

CodePudding user response:

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.

  • Related