-1

Take the following array:

["red", "red", "green", "red", "green", "blue"]

I want to turn this into an array of objects which includes the number of occurrences, like:

[
 { name: red, count: 3 },
 { name: green, count: 2 },
 { name: blue, count: 1 },
]

How can I achieve this in Javascript? I use the Lodash library so +1 if there is a simple solution using that.

Matt Saunders
  • 4,073
  • 7
  • 33
  • 47
  • Does this answer your question? [group array and get count](https://stackoverflow.com/questions/52711740/group-array-and-get-count) - the output is slightly different but it's nearly the same logic for grouping/counting. – chazsolo Mar 13 '20 at 13:17
  • No, because I need the name and count keys in there – Matt Saunders Mar 13 '20 at 13:21

4 Answers4

2

What you want to achieve is a frequency map. First, you can build a map of frequencies. By using the value in the array as a key, you can easily increment the count in the map.

The initial value for any newly-added key is: ((freq[val] || 0) + 1). This just means, grab the last value or 0, and increment by 1.

Once you have a map, you can then convert that to an array of name/count pairs.

let arr = ["red", "red", "green", "red", "green", "blue"];

let frequencyMap = arr.reduce((freq, val) => {
  return { ...freq, [val]: ((freq[val] || 0) + 1) }
}, {});

let results = Object.keys(frequencyMap).map(key => {
  return { name : key, count : frequencyMap[key] }
});

console.log(results);
.as-console-wrapper { top: 0; max-height: 100% !important; }

Code Golf

Here is a one-liner with a mapping function. :)

const freqArray = (a, fn) => ((f) => Object.keys(f).map(k => fn(k, f[k])))(a.reduce((m, v) => ({ ...m, [v]: ((m[v] || 0) + 1) }), {}));

let arr = [ "red", "red", "green", "red", "green", "blue" ];
let freq = freqArray(arr, (k, v) => ({ name : k, count : v }));

console.log(freq);
.as-console-wrapper { top: 0; max-height: 100% !important; }

Sorting the results

You could also sort the results by name, but if you do (since the keys are unique) there will be no need to sort by count, because the names will be unique.

const freqArray = (arr, opts) => ((freq) => Object.keys(freq).map(key => opts.mapFn(key, freq[key])))(arr.reduce((m, v) => ({ ...m, [v]: ((m[v] || 0) + 1) }), {})).sort(opts.cmpFn);

let arr = [ "red", "red", "green", "red", "green", "blue" ];
let freq = freqArray(arr, {
  mapFn : (key, val) => ({ name : key, count : val }),
  cmpFn : (a, b) => a.name.localeCompare(b.name) || (a.count - b.count)
});

console.log(freq);
.as-console-wrapper { top: 0; max-height: 100% !important; }

You can reverse the sort condition to sort by count (desc) then name:

const freqArray = (arr, opts) => ((freq) => Object.keys(freq).map(key => opts.mapFn(key, freq[key])))(arr.reduce((m, v) => ({ ...m, [v]: ((m[v] || 0) + 1) }), {})).sort(opts.cmpFn);

let arr = [ "red", "red", "green", "red", "green", "blue", "green", "blue", "blue" ];
let freq = freqArray(arr, {
  mapFn : (key, val) => ({ name : key, count : val }),
  cmpFn : (a, b) => (b.count - a.count) || a.name.localeCompare(b.name)
});

console.log(freq);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
1

As you're using lodash, you could use _.countBy() to count the number of times a particular word is repeated, giving you the following object as the result:

{
  "red": 3,
  "green": 2,
  "blue": 1
}

Once you have this object, you can then use _.map() to map it such that the key becomes the name property value, and the value becomes the count property value:

const arr = ["red", "red", "green", "red", "green", "blue"];

const getFreq = _.flow(
  _.countBy,
  gr => _.map(gr, (count, name) => ({name, count}))
);

console.log(getFreq(arr));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Here _.flow() is used to produce a function which can then be used to group additional arrays if needed, however, the above could be simplified if you're just after the output:

const res = _(arr).countBy().map((count, name) => ({name, count})).value();
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
0

Use Array.reduce starting with an empty array, on each iteration look for an object having the current value as name, if so, increment the count, otherwise push a new object :

const arr = ["red", "red", "green", "red", "green", "blue"];

const result = arr.reduce((acc, curr) => {
  const ndx = acc.findIndex(e => e.name === curr);

  if (ndx > -1) {
    acc[ndx].count++;
  } else {
    acc.push({
      name: curr,
      count: 1
    });
  }
  return acc;
}, []);

console.log(result);
Taki
  • 17,320
  • 4
  • 26
  • 47
0

here we use countBy() from lodash; readable and simple.

const arr = ["red", "red", "green", "red", "green", "blue"];
const result = [];

for ( [key, value] of Object.entries(_.countBy(arr)) ){
  result.push( { 
    name : key,
    count: value
  })
}

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Mechanic
  • 5,015
  • 4
  • 15
  • 38