2

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?

3
  • 2
    Change the logic and use an array (myArray.reduce((acc, line) => ..., [])), or convert the result afterwards with Object.entries() + .map() Commented Jan 27, 2020 at 16:50
  • Can you provide some context regarding why you are trying to achieve? What do you use/need the verified and unverified properties for? Commented Jan 27, 2020 at 16:59
  • Thanks for the swift answers guys! I'll be using the output as input to a d3.js stacked bar chart with monthly figures. Commented Jan 27, 2020 at 17:16

6 Answers 6

3

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

Sign up to request clarification or add additional context in comments.

Comments

2

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

Comments

2

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

Comments

1

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

5 Comments

Someone seems to be downvoting all the answers.
Might just be punishment for answering questions that don't show attempt. But I think the OP's code is close enough, it's not a "write my code for me" question.
@Barmar This attempt is enough. I mean its too much in comparison with the question I usually see of this type.
@MaheerAli Exactly. The reduce code is the hard part.
thanks, was not me downvoting, happy with the assistance
1

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

1 Comment

Someone seems to be downvoting all the answers here.
1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.