2

I need to convert an array like the following:

["Cats", "Cats", "Dogs", "Dogs", "Dogs"]

the the following form:

[
    { x: "Cats", y: 35 },
    { x: "Dogs", y: 40 },
    { x: "Birds", y: 55 },
]

where y represents the number of the same elements in the array.

Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
Kevvv
  • 3,655
  • 10
  • 44
  • 90

3 Answers3

3

Highlighting use of the efficient, modern Map-based approach that can also be found in similar forms in the duplicate:

const getCounts = iterable => {
    const counts = new Map();

    for (const x of iterable) {
        counts.set(x, (counts.get(x) ?? 0) + 1);  // use || for ES6 compat
    }

    return counts;
};

Array.from(
    getCounts(["Cats", "Cats", "Dogs", "Dogs", "Dogs"]),
    ([x, y]) => ({x, y})
)
Ry-
  • 218,210
  • 55
  • 464
  • 476
1

Here's a faster version using object lookup:

const arr = ["Cats", "Cats", "Dogs", "Dogs", "Dogs", "Birds"];
const result = Object.entries(arr.reduce((acc, cur) => {
  acc[cur] = (acc[cur] || 0) + 1;
  return acc;
}, Object.create(null))).map(([x, y]) => ({x, y}));

console.log(result);

Edit

As Ry-♦ mentioned, there was a potential bug that the string "__proto__" etc. can't be count correctly. So changed from {} to Object.create(null) to avoid it.

Hao Wu
  • 17,573
  • 6
  • 28
  • 60
  • This can have conflicts with `Object.prototype` properties. Better to use a `Map` if supported (which also allows for types other than strings), or `Object.create(null)` in a pinch. – Ry- Mar 19 '20 at 02:24
0

Another approach would be to use Array#reduce() to efficiently build a dictionary of { x : Animal, y : Count } objects, and then extract the required array of values from the dictionary via Object.values():

const input = ["Cats", "Cats", "Dogs", "Dogs", "Dogs"]

/* Obtain array of values from reduced dictionary */
const result = Object.values(input.reduce((dict, value) => {
  
  /* Fetch, or insert starting item for value in dictionary */
  const item = dict[value] || { x : value, y : 0 }
  
  /* Increment y counter for item */
  item.y++;

  /* Update dictionary value with item */  
  dict[value] = item;

  return dict

}, {}));

console.log(result);

One of the advantages with this approach is that it avoids the overhead of an extra call to .map(), making it more efficent.

Dacre Denny
  • 29,664
  • 5
  • 45
  • 65