earl duke
earl duke

Reputation: 51

Reduce array of objects in javascript

I have an array of objects in javascript that i want to reduce. See code below. message with 6 or more digits is verified, fewer is unverified. I group them by group.

const myArray = [
  { group: 'groupA', message: 'Text without a number', sl: '1A' },
  { group: 'groupA', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupB', message: 'Text without a number', sl: '1A' },
  { group: 'groupA', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupB', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupC', message: 'Text without a number', sl: '1B' },
  { group: 'groupD', message: 'Text with a number Tick0127866', sl: '1B' },
  { group: 'groupC', message: 'Text without a number', sl: '1A' }
];

output = myArray.reduce((acc, line) => {
  let yes = 0;
  let no = 0;

  line.message.match(/\d{6,}/) ? yes++ : no++;

  acc[line.group] = acc[line.group] || {};
  acc[line.group].verified = (acc[line.group].verified || 0) + yes;
  acc[line.group].unverified = (acc[line.group].unverified || 0) + no;
  return acc;
}, {});

console.log('output', output);

However I would like the output to also be an array of objects like so:

[
  { group: 'groupA', verified: 2, unverified: 1 },
  { group: 'groupB', verified: 1, unverified: 1 },
  { group: 'groupC', verified: 0, unverified: 2 },
  { group: 'groupD', verified: 1, unverified: 0 }
]

How would I do this?

Upvotes: 2

Views: 1133

Answers (6)

Rob Monhemius
Rob Monhemius

Reputation: 5144

I couldn't resist refactoring your code a little. That said. what I have done:

  • initiate the accumulator with an array ( [] )
  • push a group to the array if it is not yet initialised
  • increment verified or unverified depending whether your test fails

const myArray = [
  { group: "groupA", message: "Text without a number", sl: "1A" },
  { group: "groupA", message: "Text with a number WO5467829", sl: "1A" },
  { group: "groupB", message: "Text without a number", sl: "1A" },
  { group: "groupA", message: "Text with a number WO5467829", sl: "1A" },
  { group: "groupB", message: "Text with a number WO5467829", sl: "1A" },
  { group: "groupC", message: "Text without a number", sl: "1B" },
  { group: "groupD", message: "Text with a number Tick0127866", sl: "1B" },
  { group: "groupC", message: "Text without a number", sl: "1A" }
];

output = myArray.reduce((acc, line) => {
  if( acc.findIndex(e => e.group === line.group) === -1 ) {
  	acc.push({
      group: line.group,
      verified: 0,
      unverified: 0
    });
  }
  
  let group = acc.find(e => e.group === line.group);

  if( line.message.match(/\d{6,}/) )
  	group.verified++;
  else
  	group.unverified++;

  return acc;
}, []);

console.log(output);

Upvotes: 2

Kunal Mukherjee
Kunal Mukherjee

Reputation: 5853

Use reduce in combination with regex testing like this -

const myArray = [{
    group: 'groupA',
    message: 'Text without a number',
    sl: '1A'
  },
  {
    group: 'groupA',
    message: 'Text with a number WO5467829',
    sl: '1A'
  },
  {
    group: 'groupB',
    message: 'Text without a number',
    sl: '1A'
  },
  {
    group: 'groupA',
    message: 'Text with a number WO5467829',
    sl: '1A'
  },
  {
    group: 'groupB',
    message: 'Text with a number WO5467829',
    sl: '1A'
  },
  {
    group: 'groupC',
    message: 'Text without a number',
    sl: '1B'
  },
  {
    group: 'groupD',
    message: 'Text with a number Tick0127866',
    sl: '1B'
  },
  {
    group: 'groupC',
    message: 'Text without a number',
    sl: '1A'
  }
];

const numberRegex = /[0-9]{6}/;

const res = Object.values(myArray.reduce((acc, curr) => {
  const { group, message } = curr;
  const isPhoneVerified = numberRegex.test(message);

  if (!acc[group] && isPhoneVerified) {
    acc[group] = { group: group, verified: 1, unverified: 0 };
  } else if (!acc[group] && !isPhoneVerified) {
    acc[group] = { group: group, verified: 0, unverified: 1 };
  } else if (acc[group] && isPhoneVerified) {
    acc[group].verified += 1;
  } else if (acc[group] && !isPhoneVerified) {
    acc[group].unverified += 1;
  }

  return acc;

}, {}));

console.log(res);

Upvotes: 1

Maheer Ali
Maheer Ali

Reputation: 36594

You can use map() on Object.entries. I also refactored your code in reduce method

const myArray = [
  { group: 'groupA', message: 'Text without a number', sl: '1A' },
  { group: 'groupA', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupB', message: 'Text without a number', sl: '1A' },
  { group: 'groupA', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupB', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupC', message: 'Text without a number', sl: '1B' },
  { group: 'groupD', message: 'Text with a number Tick0127866', sl: '1B' },
  { group: 'groupC', message: 'Text without a number', sl: '1A' }
];

let output = myArray.reduce((acc, line) => {
  acc[line.group] = acc[line.group] || {verified: 0, unverified: 0};
  if(line.message.match(/\d{6,}/)){
    acc[line.group].verified++
  }
  else{
    acc[line.group].unverified++
  }

  return acc;
}, {});

output = Object.entries(output).map(([k, v]) => ({group: k, ...v}))

console.log('output', output);

Upvotes: 1

Taki
Taki

Reputation: 17664

Use an array as the initial value of your accumulator and inside .reduce , use findIndex to check for the current group, if found, update verified and unverified values, otherwise, insert a new one :

const myArray = [
  { group: "groupA", message: "Text without a number", sl: "1A" },
  { group: "groupA", message: "Text with a number WO5467829", sl: "1A" },
  { group: "groupB", message: "Text without a number", sl: "1A" },
  { group: "groupA", message: "Text with a number WO5467829", sl: "1A" },
  { group: "groupB", message: "Text with a number WO5467829", sl: "1A" },
  { group: "groupC", message: "Text without a number", sl: "1B" },
  { group: "groupD", message: "Text with a number Tick0127866", sl: "1B" },
  { group: "groupC", message: "Text without a number", sl: "1A" }
];

output = myArray.reduce((acc, line) => {
  let yes = 0;
  let no = 0;

  line.message.match(/\d{6,}/) ? yes++ : no++;

  // check if you have the group in the accumulator
  const ndx = acc.findIndex(e => e.group === line.group);

  // if you have it, manipulate verified and unverified
  if (ndx > -1) {
    acc[ndx].verified = (acc[ndx].verified || 0) + yes;
    acc[ndx].unverified = (acc[ndx].unverified || 0) + no;
  } else {
    // insert a new entry instead
    acc.push({
      group: line.group,
      verified: yes,
      unverified: no
    });
  }

  return acc;
}, []); // use an array as the initial value of the accumulator

console.log(output);

Upvotes: 3

Zohaib Ijaz
Zohaib Ijaz

Reputation: 22925

You can simply extend your solution/output to get the actual required output using Object.entries() or Object.keys(). I am going to use Object.entries()

const myArray = [
  { group: 'groupA', message: 'Text without a number', sl: '1A' },
  { group: 'groupA', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupB', message: 'Text without a number', sl: '1A' },
  { group: 'groupA', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupB', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupC', message: 'Text without a number', sl: '1B' },
  { group: 'groupD', message: 'Text with a number Tick0127866', sl: '1B' },
  { group: 'groupC', message: 'Text without a number', sl: '1A' }
];

output = myArray.reduce((acc, line) => {
  let yes = 0;
  let no = 0;

  line.message.match(/\d{6,}/) ? yes++ : no++;

  acc[line.group] = acc[line.group] || {};
  acc[line.group].verified = (acc[line.group].verified || 0) + yes;
  acc[line.group].unverified = (acc[line.group].unverified || 0) + no;
  return acc;
}, {});

console.log('output', output);

const actualOutput = Object.entries(output).map(([group, value]) => ({group, ...value }));

console.log(actualOutput);

Upvotes: 2

Maxime Girou
Maxime Girou

Reputation: 1570

Put the groupprop in your acc. Then use Object.values function to return the values of an object.

const myArray = [
  { group: 'groupA', message: 'Text without a number', sl: '1A' },
  { group: 'groupA', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupB', message: 'Text without a number', sl: '1A' },
  { group: 'groupA', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupB', message: 'Text with a number WO5467829', sl: '1A' },
  { group: 'groupC', message: 'Text without a number', sl: '1B' },
  { group: 'groupD', message: 'Text with a number Tick0127866', sl: '1B' },
  { group: 'groupC', message: 'Text without a number', sl: '1A' }
];

output = myArray.reduce((acc, line) => {
  let yes = 0;
  let no = 0;

  line.message.match(/\d{6,}/) ? yes++ : no++;

  acc[line.group] = acc[line.group] || {};
  acc[line.group].group = line.group;
  acc[line.group].verified = (acc[line.group].verified || 0) + yes;
  acc[line.group].unverified = (acc[line.group].unverified || 0) + no;
  return acc;
}, {});

console.log('output', Object.values(output));

Upvotes: 1

Related Questions