How to count the number of instances of each type?

Viewed 92

const data = ['soccer', 'soccer', 'basketball', 'basketball', 'badminton', 'swimming', 'soccer', 'squash', 'badminton', 'swimming', 'soccer', 'squash', 'soccer', 'basketball', 'fencing'];

const sports = data.reduce(function(obj, item) {
  if (!obj[item]) //to create an object if it doesn't exist
    obj[item] = 0;
  else
    obj[item]++; //to increment the object count if it exist
  return obj;
}, {});

console.log(sports);

Output I'm receiving:

Object { soccer: 0, basketball: 0, badminton: 0, swimming: 0, squash: 0, fencing: 0 }

Output I want:

Object { soccer: 5, basketball: 3, badminton: 2, swimming: 2, squash: 2, fencing: 1 }

What's going wrong?

3 Answers

In JavaScript, zero evaluates to false.
So !obj[item] is true when obj[item] == 0 and nothing ever gets incremented.

const obj = {
  'soccer': 0
};

// true
console.log(!obj['basketball']);

// also true!
console.log(!obj['soccer']);

One solution is to initialize new properties to 1 rather than 0. No value can ever be zero anyway because each property exists at least once in the dataset, and this avoids confusing zero with undefined.

const data = ['soccer', 'soccer', 'basketball', 'basketball', 'badminton', 'swimming', 'soccer', 'squash', 'badminton', 'swimming', 'soccer', 'squash', 'soccer', 'basketball', 'fencing'];

const sports = data.reduce(function(obj, item) {
  if (!obj[item])
    obj[item] = 1; // initialize property to 1 if it is falsy
  else
    obj[item]++; // otherwise increment the value
  return obj;
}, {});

console.log(sports);

But in my opinion, the else is unnecessary and Andy's method is a bit simpler.

Just for fun, here's another method using a ternary operator:

const data = ['soccer', 'soccer', 'basketball', 'basketball', 'badminton', 'swimming', 'soccer', 'squash', 'badminton', 'swimming', 'soccer', 'squash', 'soccer', 'basketball', 'fencing'];

const sports = data.reduce(function(obj, item) {
  obj[item] = obj[item] ? obj[item] + 1 : 1;
  return obj;
}, {});

console.log(sports);

And another method from snak:

const data = ['soccer', 'soccer', 'basketball', 'basketball', 'badminton', 'swimming', 'soccer', 'squash', 'badminton', 'swimming', 'soccer', 'squash', 'soccer', 'basketball', 'fencing'];

const sports = data.reduce(function(obj, item) {
  obj[item] = (obj[item] || 0) + 1;
  return obj;
}, {});

console.log(sports);

For reference, also see:
How to increment an object property value if it exists, else set the initial value?
short javascript code for: initialize to zero or increment
Count the occurrence of each word in a phrase using javascript

Remove the else. Set the value to zero if the property doesn't exist, then add one to it.

const data = ['soccer', 'soccer', 'basketball', 'basketball', 'badminton', 'swimming', 'soccer', 'squash', 'badminton', 'swimming', 'soccer', 'squash', 'soccer', 'basketball','fencing'];

const sports = data.reduce(function(obj, item){
    if(!obj[item]) obj[item] = 0;
    obj[item]++;
    return obj;
 }, {});

console.log(sports);

Additional to other answers: this can be a one liner:

const data = ['soccer', 'soccer', 'basketball', 'basketball', 'badminton', 
  'swimming', 'soccer', 'squash', 'badminton', 'swimming', 'soccer', 
  'squash', 'soccer', 'basketball', 'fencing'];
console.log(
  data.reduce( (acc, value) => ({...acc, [value]: (acc[value] || 0) + 1}), {} )
);

Related