Joe Saad
Joe Saad

Reputation: 1950

Using map and reduce to get array of objects

I'm having an array of

var c = [{name: 'John'}, {name: 'John'}, {name: 'Tom'}];

I want to use the reduce method and map to get this following result:

result = [
    { name: "John", occurence: 2 },
    { name: "Tom", occurence: 1 }
  ]

Here's an attempt I have tried but still not fully getting it. https://jsfiddle.net/joeSaad/up3ddfzz/

c = [{
  name: 'John'
}, {
  name: 'John'
}, {
  name: 'Simon'
}];

var d = c.reduce((countMap, word) => {
  countMap[word.name] = ++countMap[word.name] || 1;
  return countMap
}, []);

var e = c.reduce((countMap, word) => {
  q = [];
  countMap[word.name] = ++countMap[word.name] || 1;
  var p = {
    name: word.name,
    occurence: countMap[word.name]
  }
  q.push(p);
  return q
}, []);

console.log(e);

Help much appreciated. Thanks in advance

Upvotes: 0

Views: 131

Answers (1)

Mulan
Mulan

Reputation: 135197

You're definitely on the right track working with reduce, but I would separate the computations into two distinct steps

let c = [{name: 'John'}, {name: 'John'}, {name: 'Tom'}];

const pairs = o=> Object.keys(o).map(k=> [k, o[k]])

let count = c.reduce((acc, {name}) => {
  if (acc[name] === undefined)
    return Object.assign(acc, { [name]: 1 })
  else
    return Object.assign(acc, { [name]: acc[name] + 1 })
}, {})

var result = pairs(count).map(([name, occurences]) => ({name, occurences}))

console.log(result)
//=> [ { name: 'John', occurences: 2 }, { name: 'Tom', occurences: 1 } ]


You could abstract these behaviours into distinct functions like this

// convert an object to [[key,value]] pairs
const pairs = o=> Object.keys(o).map(k=> [k, o[k]])

// count unique instances of a property value in an array of objects
const countBy = (prop, xs)=> {
  return xs.reduce((acc, x)=> {
    let y = x[prop]
    if (acc[y] === undefined)
      return Object.assign(acc, { [y]: 1 })
    else
      return Object.assign(acc, { [y]: acc[y] + 1 })
  }, {})
}

// your data
let c = [{name: 'John'}, {name: 'John'}, {name: 'Tom'}]

// then chain it all together
let result = pairs(countBy('name', c)).map(([name, occurences]) => ({name, occurences}))

console.log(result)
//=> [ { name: 'John', occurences: 2 }, { name: 'Tom', occurences: 1 } ]


ES6 also offers Map which is pretty good for doing this kind of computation.

This code reads a little nicer to me and is a little more semantically correct. Our previous code was doing the exact same thing, only it was using a plain object. Map is a data structure specifically designed for this key->value data, so that makes Map a better choice.

The only problem with this is, if your codebase isn't already using Maps in some way, introducing them just for this might be the wrong move.

// convert a Map to an array of pairs
const mpairs = m=> [...m.entries()]

// countBy this time uses Map
const countBy = (prop, xs)=> {
  return xs.reduce((m, x)=> {
    let y = x[prop]
    if (m.has(y))
      return m.set(y, m.get(y) + 1)
    else
      return m.set(y, 1)
  }, new Map)
}

// your data
let c = [{name: 'John'}, {name: 'John'}, {name: 'Tom'}]

// then chain it all together
let result = mpairs(countBy('name', c)).map(([name, occurences]) => ({name, occurences}))

console.log(result)
//=> [ { name: 'John', occurences: 2 }, { name: 'Tom', occurences: 1 } ]

Upvotes: 3

Related Questions