How to count occurrences of a value within an array?

Asked

Viewed 2,518 times

3

How to take the amount of times a string repeats within an array?

Ex:

var teste = ["oi", "tudo", "bem", "oi"];

4 answers

5

Just to complement the another answer: use filter actually works, but you have to remember that filter returns another array with the filtration results.

If you want to know only the amount of elements and nothing else, is it worth creating another array, just to get the size? Wouldn’t it be better to have a counter?

var teste = ["oi", "tudo", "bem", "oi"];
var qtd = 0;
for (var i = 0; i < teste.length; i++) {
    if (teste[i] === "oi") qtd++;
}
console.log(qtd); // 2

Or, using for...of:

var teste = ["oi", "tudo", "bem", "oi"];
var qtd = 0;
for (var s of teste) {
    if (s === "oi") qtd++;
}
console.log(qtd); // 2

"Ah, but filter does everything in a row."

So what? Code "smaller" is not necessarily "better". For example, filter is slower than the loops. Of course, for a few small arrays the difference will be insignificant and may not be anything more than micro-optimization, and it is always good to test with real cases to know if it is a problem. But anyway, it’s interesting to know the details of everything you use and choose with more objective criteria than "is shorter". Often a smaller code can hide some extra complexity (such as having to call several times a function - yes, the parameter you pass to filter is a function - and yet create another array to store the results).

Not that it’s a big mistake to use filter, I just wanted to deepen the discussion a little to show that there are times when doing things "at hand" is not such a bad alternative.


Another reason I don’t prefer filter in this specific case: why generate an array where all elements are equal?

var teste = ["oi", "tudo", "bem", "oi"];
console.log(teste.filter(x => x === "oi")); // [ "oi", "oi" ]

filter It is interesting if the criterion can return different elements. For example, if it were "all strings starting with a certain letter", then it might make sense to return the array (but only if you want to know which ones are those elements; if you want to know only how many are, I still find it an "exaggeration" to create another array just to catch the size).

4


4

The question has already been answered with the use of filter (here) and with the use of for (here), both are valid solutions to the problem, but I want to bring another alternative, also using one of the methods of the Array, which is the reduce.

The reduce execute a function given for each element of the Array and accumulate a result, and you define what that result is (it can be a number, an array, a string...).

For the counting of occurrences, we can do:

var teste = ["oi", "tudo", "bem", "oi"];
var qtd = teste.reduce((total, valor) => {
  if (valor === "oi") {
    return total + 1;
  }
  return total;
}, 0); // 0 é o "total" inicial

console.log(qtd); // 2

In this case, the reduce, as well as the for, will not create a new Array unnecessarily, unlike filter.

Another point of observation is that with the use of reduce you can store the result in a constant (const).

4

The other three answers count how many times one string repeats within an array, but in the possibility of several strings repeat?

In this case you could "adapt" the code of the other answers and count one for each repeated word. But obviously this would not be efficient, since it would have to go through the entire array for every word that needed to be checked.

A relatively simple way to solve this problem (relatively efficiently) is to use an object as a counter. In this way, we would count the occurrence of each string inside the array, scanning it only once.

Behold:

const arr = ['oi', 'oi', 'oi', 'tudo', 'tudo', 'bem', '?'];
console.log(countDuplicates(arr));

function countDuplicates(arr) {
  const map = Object.create(null);
  
  for (const str of arr) {
    if (map[str]) {
      // Se já tiver contabilizado, some `1` ao contador:
      map[str] += 1;
    } else {
      // Caso contrário, iniciamos o contador como `1`:
      map[str] = 1;
    }
  }
  
  return map;
}

But note that the object will return all elements of the array, including those that appeared only once. To solve this, one can iterate over the properties of the created object and show only those with value greater than 1. Thus:

const arr = ['oi', 'oi', 'oi', 'tudo', 'tudo', 'bem', '?'];
console.log(countDuplicates(arr));

function countDuplicates(arr) {
  // Nosso mapa (objeto vazio sem protótipo):
  const map = Object.create(null);
  
  for (const str of arr) {
    map[str] = (map[str] || 0) + 1;
  }
  
  const repeatedMap = Object.create(null);
  for (const prop in map) {
    if (map[prop] > 1) {
      repeatedMap[prop] = map[prop];
    }
  }
  
  return repeatedMap;
}

Note that I also removed the if/else of the first for, abbreviating for the expression (map[str] || 0) + 1. Learn more.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.