Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatically find the number of occurrences a given value has in an array

I have an array with repeating values. I would like to find the number of occurrences for any given value.

For example, if I have an array defined as so: var dataset = [2,2,4,2,6,4,7,8];, I want to find the number of occurrences of a certain value in the array. That is, the program should show that if I have 3 occurrences of the value 2, 1 occurrence of the value 6, and so on.

What's the most idiomatic/elegant way to do this?

like image 516
Shrey Gupta Avatar asked Jun 26 '13 06:06

Shrey Gupta


People also ask

How do you count the number of occurrences in an array?

To count the occurrences of each element in an array: Declare a variable that stores an empty object. Use the for...of loop to iterate over the array. On each iteration, increment the count for the current element if it exists or initialize the count to 1 .

How do you count occurrences in JavaScript?

In JavaScript, we can count the string occurrence in a string by counting the number of times the string present in the string. JavaScript provides a function match(), which is used to generate all the occurrences of a string in an array.


1 Answers

reduce is more appropriate here than filter as it doesn't build a temporary array just for counting.

var dataset = [2,2,4,2,6,4,7,8];  var search = 2;    var count = dataset.reduce(function(n, val) {      return n + (val === search);  }, 0);    console.log(count);

In ES6:

let count = dataset.reduce((n, x) => n + (x === search), 0); 

Note that it's easy to extend that to use a custom matching predicate, for example, to count objects that have a specific property:

people = [      {name: 'Mary', gender: 'girl'},      {name: 'Paul', gender: 'boy'},      {name: 'John', gender: 'boy'},      {name: 'Lisa', gender: 'girl'},      {name: 'Bill', gender: 'boy'},      {name: 'Maklatura', gender: 'girl'}  ]    var numBoys = people.reduce(function (n, person) {      return n + (person.gender == 'boy');  }, 0);    console.log(numBoys);

Counting all items, that is, making an object like {x:count of xs} is complicated in javascript, because object keys can only be strings, so you can't reliably count an array with mixed types. Still, the following simple solution will work well in most cases:

count = function (ary, classifier) {      classifier = classifier || String;      return ary.reduce(function (counter, item) {          var p = classifier(item);          counter[p] = counter.hasOwnProperty(p) ? counter[p] + 1 : 1;          return counter;      }, {})  };    people = [      {name: 'Mary', gender: 'girl'},      {name: 'Paul', gender: 'boy'},      {name: 'John', gender: 'boy'},      {name: 'Lisa', gender: 'girl'},      {name: 'Bill', gender: 'boy'},      {name: 'Maklatura', gender: 'girl'}  ];    // If you don't provide a `classifier` this simply counts different elements:    cc = count([1, 2, 2, 2, 3, 1]);  console.log(cc);    // With a `classifier` you can group elements by specific property:    countByGender = count(people, function (item) {      return item.gender  });  console.log(countByGender);

2017 update

In ES6, you use the Map object to reliably count objects of arbitrary types.

class Counter extends Map {      constructor(iter, key=null) {          super();          this.key = key || (x => x);          for (let x of iter) {              this.add(x);          }      }      add(x) {        x = this.key(x);        this.set(x, (this.get(x) || 0) + 1);      }  }    // again, with no classifier just count distinct elements    results = new Counter([1, 2, 3, 1, 2, 3, 1, 2, 2]);  for (let [number, times] of results.entries())      console.log('%s occurs %s times', number, times);      // counting objects    people = [      {name: 'Mary', gender: 'girl'},      {name: 'John', gender: 'boy'},      {name: 'Lisa', gender: 'girl'},      {name: 'Bill', gender: 'boy'},      {name: 'Maklatura', gender: 'girl'}  ];      chessChampions = {      2010: people[0],      2012: people[0],      2013: people[2],      2014: people[0],      2015: people[2],  };    results = new Counter(Object.values(chessChampions));  for (let [person, times] of results.entries())      console.log('%s won %s times', person.name, times);    // you can also provide a classifier as in the above    byGender = new Counter(people, x => x.gender);  for (let g of ['boy', 'girl'])     console.log("there are %s %ss", byGender.get(g), g);

A type-aware implementation of Counter can look like this (Typescript):

type CounterKey = string | boolean | number;  interface CounterKeyFunc<T> {     (item: T): CounterKey; }  class Counter<T> extends Map<CounterKey, number> {     key: CounterKeyFunc<T>;      constructor(items: Iterable<T>, key: CounterKeyFunc<T>) {         super();         this.key = key;         for (let it of items) {             this.add(it);         }     }      add(it: T) {         let k = this.key(it);         this.set(k, (this.get(k) || 0) + 1);     } }  // example:  interface Person {     name: string;     gender: string; }   let people: Person[] = [     {name: 'Mary', gender: 'girl'},     {name: 'John', gender: 'boy'},     {name: 'Lisa', gender: 'girl'},     {name: 'Bill', gender: 'boy'},     {name: 'Maklatura', gender: 'girl'} ];   let byGender = new Counter(people, (p: Person) => p.gender);  for (let g of ['boy', 'girl'])     console.log("there are %s %ss", byGender.get(g), g); 
like image 78
11 revs, 2 users 99% Avatar answered Sep 25 '22 17:09

11 revs, 2 users 99%