59

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?

Shrey Gupta
  • 5,509
  • 8
  • 45
  • 71

11 Answers11

152

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);
georg
  • 211,518
  • 52
  • 313
  • 390
  • @thg435 I tried using this and ended up with an undefined value...and also, does the `reduce` method still preserve the array? I would like to keep the array unchanged. – Shrey Gupta Jun 27 '13 at 02:28
  • @Bagavatu: post your code/fiddle. No, reduce doesn't change the array. – georg Jun 27 '13 at 09:12
  • @georg I'm having type errors using your code snippet with Typescript. ```Property 'key' does not exist on type 'Counter'. Did you mean 'keys'?ts(2551) lib.es2015.iterable.d.ts(123, 5): 'keys' is declared here.``` in case you want to fix your snippet. Otherwise I'll request an edit if I figure it out myself. :) – Narshe Apr 08 '19 at 08:42
  • 1
    @Narshe: added a Typescript example. – georg Apr 08 '19 at 09:39
  • 1
    That was a very quick response. Thanks a million. I have a question though. The option for the key to be nullable has been lost with the new version. Not sure if intended: `constructor(iter, key=null)` `this.key = key || (x => x);` – Narshe Apr 08 '19 at 11:13
  • 1
    @Narshe: it would be hard to enforce typing for this one, because `x=>x` is of type `T=>T`, while the class is designed to accept `T=>CounterKey`. I'd suggest splitting this into two classes: just `Counter` (without a callback) and `KeyCounter` (with a mandatory callback). – georg Apr 08 '19 at 11:41
  • I see, and totally agree. I'm press the upvote really hard and it only increases by one. Thanks! – Narshe Apr 08 '19 at 12:39
  • Please, don't use reduce directly, even you will forget what it does in two weeks and then somebody will have to reverse engineer it. Put it in a nicely named function like `count` in the third example and implement the body however you'd like. – Tamas Hegedus Feb 03 '22 at 19:02
53
array.filter(c => c === searchvalue).length;
Liam
  • 27,717
  • 28
  • 128
  • 190
Julian Wagner
  • 674
  • 6
  • 16
18

Here is one way to show ALL counts at once:

var dataset = [2, 2, 4, 2, 6, 4, 7, 8];
var counts = {}, i, value;
for (i = 0; i < dataset.length; i++) {
    value = dataset[i];
    if (typeof counts[value] === "undefined") {
        counts[value] = 1;
    } else {
        counts[value]++;
    }
}
console.log(counts);
// Object {
//    2: 3,
//    4: 2,
//    6: 1,
//    7: 1,
//    8: 1
//}
Salman A
  • 262,204
  • 82
  • 430
  • 521
15

Newer browsers only due to using Array.filter

var dataset = [2,2,4,2,6,4,7,8];
var search = 2;
var occurrences = dataset.filter(function(val) {
    return val === search;
}).length;
console.log(occurrences); // 3
Ninjakannon
  • 3,751
  • 7
  • 53
  • 76
goat
  • 31,486
  • 7
  • 73
  • 96
10
const dataset = [2,2,4,2,6,4,7,8];
const count = {};

dataset.forEach((el) => {
    count[el] = count[el] + 1 || 1
});

console.log(count)

//  {
//    2: 3,
//    4: 2,
//    6: 1,
//    7: 1,
//    8: 1
//  }
Manolis
  • 893
  • 1
  • 14
  • 33
  • He checks on every loop if the key already exists. If yes increment the current value of the by one. If not insert 1 for this key. – TdoubleG Sep 28 '17 at 09:06
6

Using a normal loop, you can find the occurrences consistently and reliably:

const dataset = [2,2,4,2,6,4,7,8];

function getNumMatches(array, valToFind) {
    let numMatches = 0;
    for (let i = 0, j = array.length; i < j; i += 1) {
        if (array[i] === valToFind) {
            numMatches += 1;
        }
    }
    return numMatches;
}

alert(getNumMatches(dataset, 2)); // should alert 3

DEMO: https://jsfiddle.net/a7q9k4uu/

To make it more generic, the function could accept a predicate function with custom logic (returning true/false) which would determine the final count. For example:

const dataset = [2,2,4,2,6,4,7,8];

function getNumMatches(array, predicate) {
    let numMatches = 0;
    for (let i = 0, j = array.length; i < j; i += 1) {
        const current = array[i];
        if (predicate(current) === true) {
            numMatches += 1;
        }
    }
    return numMatches;
}

const numFound = getNumMatches(dataset, (item) => {
    return item === 2;
});

alert(numFound); // should alert 3

DEMO: https://jsfiddle.net/57en9nar/1/

Ian
  • 50,146
  • 13
  • 101
  • 111
  • +1 For wrapping in a semantically understandable and readable function. Although I would call it `getNumMatches` or `getNumOccurrences`. `findOccurrences` sounds like you're trying to return the occurrences themselves (an array) rather than how many of them there are (a number). – Adam Zerner Feb 23 '18 at 17:52
  • Also, as others have mentioned, you can pass in a predicate function instead of a value to test for matches. Demo: https://repl.it/repls/DecimalFrostyTechnicians – Adam Zerner Feb 23 '18 at 17:57
  • @AdamZerner Agreed, thanks for the input. Updated with your recommendations – Ian Feb 23 '18 at 18:05
4

You can count all items in an array, in a single line, using reduce.

[].reduce((a,b) => (a[b] = a[b] + 1 || 1) && a, {})

This will yield an object, whose keys are the distinct elements in the array and values are the count of occurences of elements in the array. You can then access one or more of the counts by accessing a corresponding key on the object.

For example if you were to wrap the above in a function called count():

function count(arr) {
  return arr.reduce((a,b) => (a[b] = a[b] + 1 || 1) && a, {})
}

count(['example'])          // { example: 1 }
count([2,2,4,2,6,4,7,8])[2] // 3
justin.m.chase
  • 13,061
  • 8
  • 52
  • 100
2

You can do with use of array.reduce(callback[, initialValue]) method in JavaScript 1.8

var dataset = [2,2,4,2,6,4,7,8],
    dataWithCount = dataset.reduce( function( o , v ) {

        if ( ! o[ v ] ) {
            o[ v ] = 1 ;  
        }  else {
            o[ v ] = o[ v ] + 1;
        }      

        return o ;    

    }, {} );

// print data with count.
for( var i in  dataWithCount ){
     console.log( i + 'occured ' + dataWithCount[i] + 'times ' ); 
}

// find one number
var search = 2,
    count = dataWithCount[ search ] || 0;
rab
  • 4,134
  • 1
  • 29
  • 42
1

I've found it more useful to end up with a list of objects with a key for what is being counted and a key for the count:

const data = [2,2,4,2,6,4,7,8]
let counted = []
for (var c of data) {
  const alreadyCounted = counted.map(c => c.name)
  if (alreadyCounted.includes(c)) {
    counted[alreadyCounted.indexOf(c)].count += 1
  } else {
    counted.push({ 'name': c, 'count': 1})
  }
}
console.log(counted)

which returns:

[ { name: 2, count: 3 },
  { name: 4, count: 2 },
  { name: 6, count: 1 },
  { name: 7, count: 1 },
  { name: 8, count: 1 } ]

It isn't the cleanest method, and if anyone knows how to achieve the same result with reduce let me know. However, it does produce a result that's fairly easy to work with.

crash springfield
  • 1,072
  • 7
  • 17
  • 33
0

First, you can go with Brute Force Solution by going with Linear Search.

public int LinearSearchcount(int[] A, int data){
  int count=0;
  for(int i=0;i<A.length;i++) {
    if(A[i]==data) count++;
  }
  return count;
}

However, for going with this, we get Time complexity as O(n).But by going with Binary search, we can improve our Complexity.

coudy.one
  • 1,382
  • 12
  • 24
0

If you try to do it this way, you might get an error like the one below.

array.reduce((acc, arr) => acc + (arr.label === 'foo'), 0); // Operator '+' cannot be applied to type 'boolean'.

One solution would be to do it this way

array = [
    { id: 1, label: 'foo' },
    { id: 2, label: 'bar' },
    { id: 3, label: 'foo' },
    { id: 4, label: 'bar' },
    { id: 5, label: 'foo' }
]

array.reduce((acc, arr) => acc + (arr.label === 'foo' ? 1 : 0), 0); // result: 3