Counting the occurrences / frequency of array elements
Asked Answered
L

42

356

In Javascript, I'm trying to take an initial array of number values and count the elements inside it. Ideally, the result would be two new arrays, the first specifying each unique element, and the second containing the number of times each element occurs. However, I'm open to suggestions on the format of the output.

For example, if the initial array was:

5, 5, 5, 2, 2, 2, 2, 2, 9, 4

Then two new arrays would be created. The first would contain the name of each unique element:

5, 2, 9, 4

The second would contain the number of times that element occurred in the initial array:

3, 5, 1, 1

Because the number 5 occurs three times in the initial array, the number 2 occurs five times and 9 and 4 both appear once.

I've searched a lot for a solution, but nothing seems to work, and everything I've tried myself has wound up being ridiculously complex. Any help would be appreciated!

Thanks :)

Ladylike answered 14/4, 2011 at 18:32 Comment(4)
If all you needed was to see if a value appears only once (instead of two or more times), you could use if (arr.indexOf(value) == arr.lastIndexOf(value))Levin
We can use ramda.js to achieve this the easy way. const ary = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]; R.countBy(r=> r)(ary)Diptych
arr.filter(x => x===5).length would return 3 to indicate that there are '3' fives in the array.Thermopylae
Let us assume My response is array of objectHarrisharrisburg
C
117

const arr = [2, 2, 5, 2, 2, 2, 4, 5, 5, 9];

function foo (array) {
  let a = [],
    b = [],
    arr = [...array], // clone array so we don't change the original when using .sort()
    prev;

  arr.sort();
  for (let element of arr) {
    if (element !== prev) {
      a.push(element);
      b.push(1);
    }
    else ++b[b.length - 1];
    prev = element;
  }

  return [a, b];
}

const result = foo(arr);
console.log('[' + result[0] + ']','[' + result[1] + ']')
console.log(arr)
Cleome answered 14/4, 2011 at 18:58 Comment(5)
has side-effect of sorting the array (side effects are bad), also sorting is O(N log(N)) and the elegance gain isn't worth itLowry
In absence of a nice high-level primitive from a third-party library, I would normally implement this like the reduce answer. I was about to submit such an answer before I saw it already existed. Nevertheless the counts[num] = counts[num] ? counts[num]+1 : 1 answer also works (equivalent to the if(!result[a[i]])result[a[i]]=0 answer, which is more elegant but less easy to read); this answers can be modified to use a "nicer" version of the for loop, perhaps a third-party for-loop, but I sort of ignored that since the standard index-based for-loops are sadly the default.Lowry
For small arrays sorting it in-place can be faster than creating an associative array.Tabriz
@ŠimeVidas I added a disclaimer for Array.sort, because missing this fact has tripped me up in real code. (It's easy to naively treat it as if it makes a copy, since it returns the sorted array.)Doyon
Agree with @ninjagecko. dictionary would be better. Here is my answer to another approach.Commissariat
M
345

You can use an object to hold the results:

const arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
const counts = {};

for (const num of arr) {
  counts[num] = counts[num] ? counts[num] + 1 : 1;
}

console.log(counts);
console.log(counts[5], counts[2], counts[9], counts[4]);

So, now your counts object can tell you what the count is for a particular number:

console.log(counts[5]); // logs '3'

If you want to get an array of members, just use the keys() functions

keys(counts); // returns ["5", "2", "9", "4"]
Marcelline answered 14/4, 2011 at 18:42 Comment(9)
It should be pointed out, that Object.keys() function is only supported in IE9+, FF4+, SF5+, CH6+ but Opera doesn't support it. I think the biggest show stopper here is IE9+.Bevin
Similarly, I also like counts[num] = (counts[num] || 0) + 1. That way you only have to write counts[num] twice instead of three times on that one line there.Petta
This is a nice answer. This is easily abstracted into a function that accepts an array and returns a 'counts' object.Tolu
This is true for the specific example in the question, but for the sake of googlers it's worth pointing out that this is not always a safe technique for wider usage. Storing the values as object keys to count them means you're casting those values to strings and then counting that value. [5, "5"] will simply say you've got "5" two times. Or counting instances some different objects is just gonna tell you there's a lot of [object Object]. Etc. etc.Harappa
How could I then filter the returned object to show me highest to lowest, or lowest to highest count on a numbersBinucleate
Very useful counter. Can easily be used in the reduce() method. Thanks!Stanislas
why not: var arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]; var counts = {}; arr.forEach(function(num) { if (!(num in counts)) { counts[num] = 0; } counts[num]++; }); console.log(counts); This meets the OP's criteria of a way to get the unique values and the counts, but instead of using two separate arrays, a single object handles both requirements.Vivle
Note that elements in the array are always converted to a string when used as an object key. So when you would pass [1, "1", { toString: () => "1" }] you would give the result { 1: 3 }Sundew
Can someone explain why the expression to execute if the condition is falsy (counts[num]) is 1 and not 0?Unpaged
S
197

const occurrences = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4].reduce(function (acc, curr) {
  return acc[curr] ? ++acc[curr] : acc[curr] = 1, acc
}, {});

console.log(occurrences) // => {2: 5, 4: 1, 5: 3, 9: 1}
Sevastopol answered 14/4, 2011 at 21:9 Comment(4)
Thanks, very nice solution ;) ... and to get the "key" and "value" arrays: const keys = Object.keys(a); const values = Object.values(a);Deforest
Short hand: acc[curr] = (acc[curr] || 0) + 1 instead of using if/else. You can check the answer belowCommissariat
Note that elements in the array are always converted to a string when used as an object key. So when you would pass [1, "1", { toString: () => "1" }] you would give the result { 1: 3 }Sundew
@Sevastopol , What should I change to get like this : num:2,occ:5? thank you.Frilling
H
121

One line ES6 solution. So many answers using object as a map but I can't see anyone using an actual Map

const map = arr.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());

Use map.keys() to get unique elements

Use map.values() to get the occurrences

Use map.entries() to get the pairs [element, frequency]

var arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]

const map = arr.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());

console.info([...map.keys()])
console.info([...map.values()])
console.info([...map.entries()])
Homer answered 14/7, 2019 at 14:54 Comment(4)
I used this and It worked pretty fine!Prowler
I thought it'll let me use arbitrary objects as keys, but I see no way to tell it how to calculate an object id, so not of much help.Caudad
@Caudad The question is not asking about objects specifically but, changing e to e.id should be sufficient. Also, I'm not sure if id is a property that should contain duplicates, as it helps us identify elements.Homer
Yeah, the question is not about objects, so you're in a position to ignore my comment. But I came here looking for a solution to my case. And Map looked promising, but in the end it turned out that it doesn't help much.Caudad
C
117

const arr = [2, 2, 5, 2, 2, 2, 4, 5, 5, 9];

function foo (array) {
  let a = [],
    b = [],
    arr = [...array], // clone array so we don't change the original when using .sort()
    prev;

  arr.sort();
  for (let element of arr) {
    if (element !== prev) {
      a.push(element);
      b.push(1);
    }
    else ++b[b.length - 1];
    prev = element;
  }

  return [a, b];
}

const result = foo(arr);
console.log('[' + result[0] + ']','[' + result[1] + ']')
console.log(arr)
Cleome answered 14/4, 2011 at 18:58 Comment(5)
has side-effect of sorting the array (side effects are bad), also sorting is O(N log(N)) and the elegance gain isn't worth itLowry
In absence of a nice high-level primitive from a third-party library, I would normally implement this like the reduce answer. I was about to submit such an answer before I saw it already existed. Nevertheless the counts[num] = counts[num] ? counts[num]+1 : 1 answer also works (equivalent to the if(!result[a[i]])result[a[i]]=0 answer, which is more elegant but less easy to read); this answers can be modified to use a "nicer" version of the for loop, perhaps a third-party for-loop, but I sort of ignored that since the standard index-based for-loops are sadly the default.Lowry
For small arrays sorting it in-place can be faster than creating an associative array.Tabriz
@ŠimeVidas I added a disclaimer for Array.sort, because missing this fact has tripped me up in real code. (It's easy to naively treat it as if it makes a copy, since it returns the sorted array.)Doyon
Agree with @ninjagecko. dictionary would be better. Here is my answer to another approach.Commissariat
P
103

If using underscore or lodash, this is the simplest thing to do:

_.countBy(array);

Such that:

_.countBy([5, 5, 5, 2, 2, 2, 2, 2, 9, 4])
=> Object {2: 5, 4: 1, 5: 3, 9: 1}

As pointed out by others, you can then execute the _.keys() and _.values() functions on the result to get just the unique numbers, and their occurrences, respectively. But in my experience, the original object is much easier to deal with.

Photography answered 31/1, 2015 at 18:1 Comment(2)
Just worth noting that with countBy it only includes items that exist in the list, so if you wanted to count items that may not exist in the list then you would need to handle the exception. Or use lodash filter and length like this: filter([true, true, true, false], function(m){return m==true}).length. This would just return 0 if no values exist.Dateless
Worth adding that you need: const _ = require("lodash")Pinkard
I
69

Don't use two arrays for the result, use an object:

a      = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
result = { };
for(var i = 0; i < a.length; ++i) {
    if(!result[a[i]])
        result[a[i]] = 0;
    ++result[a[i]];
}

Then result will look like:

{
    2: 5,
    4: 1,
    5: 3,
    9: 1
}
Isthmian answered 14/4, 2011 at 18:49 Comment(0)
P
62

How about an ECMAScript2015 option.

const a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

const aCount = new Map([...new Set(a)].map(
    x => [x, a.filter(y => y === x).length]
));
aCount.get(5)  // 3
aCount.get(2)  // 5
aCount.get(9)  // 1
aCount.get(4)  // 1

This example passes the input array to the Set constructor creating a collection of unique values. The spread syntax then expands these values into a new array so we can call map and translate this into a two-dimensional array of [value, count] pairs - i.e. the following structure:

Array [
   [5, 3],
   [2, 5],
   [9, 1],
   [4, 1]
]

The new array is then passed to the Map constructor resulting in an iterable object:

Map {
    5 => 3,
    2 => 5,
    9 => 1,
    4 => 1
}

The great thing about a Map object is that it preserves data-types - that is to say aCount.get(5) will return 3 but aCount.get("5") will return undefined. It also allows for any value / type to act as a key meaning this solution will also work with an array of objects.

function frequencies(/* {Array} */ a){
    return new Map([...new Set(a)].map(
        x => [x, a.filter(y => y === x).length]
    ));
}

let foo = { value: 'foo' },
    bar = { value: 'bar' },
    baz = { value: 'baz' };

let aNumbers = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4],
    aObjects = [foo, bar, foo, foo, baz, bar];

frequencies(aNumbers).forEach((val, key) => console.log(key + ': ' + val));
frequencies(aObjects).forEach((val, key) => console.log(key.value + ': ' + val));
Prognosis answered 30/1, 2016 at 12:31 Comment(4)
do you have by any chance an improved answer of this just for an object array? im having trouble trying to modify it for an object array, where you just create a new array/map/set in which you remove duplicates, and add a new value for the object, let say called "duplicatedCount: value". i managed to remove duplicates in my nested objects array from this answer stackoverflow.com/a/36744732Wallasey
Set uses object references for uniqueness and offers no API for comparison of "similar" objects. If you want to use this approach for such a task you'd need some intermediate reduction function that guarantees an array of unique instances. It's not the most efficient but I put together a quick example here.Prognosis
Thanks for the answer! but i actually solved it a lilttle bit differently. if you can see the answer i added here https://mcmap.net/q/37122/-counting-the-occurrences-frequency-of-array-elements i gave example of what i did. it works well, my case had a complex object needed comparing. dont know about the efficiency of my solution thoughWallasey
This might use nice new data structures but has runtime in O() while there are plenty of simple algorithms here that solve it in O(n).Predominance
K
58

I think this is the simplest way how to count occurrences with same value in array.

var a = [true, false, false, false];
a.filter(function(value){
    return value === false;
}).length
Karlkarla answered 16/10, 2014 at 8:46 Comment(2)
or a.filter(value => !value).length with the new js syntaxBugs
Doesn’t answer the question.Dagostino
C
38

const data = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]

function count(arr) {
  return arr.reduce((prev, curr) => (prev[curr] = ++prev[curr] || 1, prev), {})
}

console.log(count(data))
Caviness answered 3/10, 2016 at 22:29 Comment(3)
Would anyone care to explain this (prev[curr] = ++prev[curr] || 1, prev) ?Urnfield
The comma operator “evaluates each of its operands (from left to right) and returns the value of the last operand”, so this increments the value of prev[curr] (or initialises it to 1), then returns prev.Br
but is the output an array?Intact
B
36

2021's version

The more elegant way is using Logical nullish assignment (x ??= y) combined with Array#reduce() with O(n) time complexity.

The main idea is still using Array#reduce() to aggregate with output as object to get the highest performance (both time and space complexity) in terms of searching & construct bunches of intermediate arrays like other answers.

const arr = [2, 2, 2, 2, 2, 4, 5, 5, 5, 9];
const result = arr.reduce((acc, curr) => {
  acc[curr] ??= {[curr]: 0};
  acc[curr][curr]++;
  
  return acc;
}, {});

console.log(Object.values(result));

Clean & Refactor code

Using Comma operator (,) syntax.

The comma operator (,) evaluates each of its operands (from left to right) and returns the value of the last operand.

const arr = [2, 2, 2, 2, 2, 4, 5, 5, 5, 9];
const result = arr.reduce((acc, curr) => (acc[curr] = (acc[curr] || 0) + 1, acc), {});
console.log(result);

Output

{
  "2": 5,
  "4": 1,
  "5": 3,
  "9": 1
}
Botchy answered 2/2, 2021 at 2:5 Comment(4)
How can I get the highest tally from this object?Fucus
Could you pls give me your expected result? @kontenurbanCommissariat
This is a very nice answer. you could make it more concise with: const result = arr.reduce((acc, curr) => (acc[curr] = -~(acc[curr]), acc), {}); See https://mcmap.net/q/37265/-counting-frequency-of-characters-in-a-string-using-javascript-duplicate for an explanation.Nylanylghau
Thanks @YoniRabinovitch. Bitwise of your answer looks elegant & concise as well.Commissariat
E
26

If you favour a single liner.

arr.reduce(function(countMap, word) {countMap[word] = ++countMap[word] || 1;return countMap}, {});

Edit (6/12/2015): The Explanation from the inside out. countMap is a map that maps a word with its frequency, which we can see the anonymous function. What reduce does is apply the function with arguments as all the array elements and countMap being passed as the return value of the last function call. The last parameter ({}) is the default value of countMap for the first function call.

Entrap answered 3/3, 2015 at 12:46 Comment(2)
You should explain this. that would make it a much better answer so people can learn how to use it in other use cases.Spermatogonium
A single liner that just removes the linebreak that would usually follow ;, { and }. ... OK. I think with that definition of a one liner we can write Conway's Game of Life as a "oneliner".Bookmaker
P
22

ES6 version should be much simplifier (another one line solution)

let arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
let acc = arr.reduce((acc, val) => acc.set(val, 1 + (acc.get(val) || 0)), new Map());

console.log(acc);
// output: Map { 5 => 3, 2 => 5, 9 => 1, 4 => 1 }

A Map instead of plain Object helping us to distinguish different type of elements, or else all counting are base on strings

Peag answered 7/9, 2017 at 7:28 Comment(0)
T
12

Edit 2020: this is a pretty old answer (nine years). Extending the native prototype will always generate discussion. Although I think the programmer is free to choose her own programming style, here's a (more modern) approach to the problem without extending Array.prototype:

{
  // create array with some pseudo random values (1 - 5)
  const arr = Array.from({length: 100})
    .map( () => Math.floor(1 + Math.random() * 5) );
  // frequencies using a reducer
  const arrFrequencies = arr.reduce((acc, value) => 
      ({ ...acc, [value]: acc[value] + 1 || 1}), {} )
  console.log(arrFrequencies);    
  console.log(`Value 4 occurs ${arrFrequencies[4]} times in arrFrequencies`);

  // bonus: restore Array from frequencies
  const arrRestored = Object.entries(arrFrequencies)
    .reduce( (acc, [key, value]) => acc.concat(Array(value).fill(+key)), [] );
  console.log(arrRestored.join());  
}
.as-console-wrapper { top: 0; max-height: 100% !important; }

The old (2011) answer: you could extend Array.prototype, like this:

{
  Array.prototype.frequencies = function() {
    var l = this.length,
      result = {
        all: []
      };
    while (l--) {
      result[this[l]] = result[this[l]] ? ++result[this[l]] : 1;
    }
    // all pairs (label, frequencies) to an array of arrays(2)
    for (var l in result) {
      if (result.hasOwnProperty(l) && l !== 'all') {
        result.all.push([l, result[l]]);
      }
    }
    return result;
  };

  var freqs = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4].frequencies();
  console.log(`freqs[2]: ${freqs[2]}`); //=> 5
  
  // or
  var freqs = '1,1,2,one,one,2,2,22,three,four,five,three,three,five'
    .split(',')
    .frequencies();
    
  console.log(`freqs.three: ${freqs.three}`); //=> 3
  
// Alternatively you can utilize Array.map:

    Array.prototype.frequencies = function() {
      var freqs = {
        sum: 0
      };
      this.map(function(a) {
        if (!(a in this)) {
          this[a] = 1;
        } else {
          this[a] += 1;
        }
        this.sum += 1;
        return a;
      }, freqs);
      return freqs;
    }
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
Tabbatha answered 14/4, 2011 at 20:2 Comment(0)
D
11

A shorter version using reduce and tilde (~) operator.

const data = [2, 2, 2, 2, 2, 4, 5, 5, 5, 9];

function freq(nums) {
  return nums.reduce((acc, curr) => {
    acc[curr] = -~acc[curr];
    return acc;
  }, {});
}

console.log(freq(data));
Dentiform answered 9/3, 2021 at 16:0 Comment(0)
C
9

Based on answer of @adamse and @pmandell (which I upvote), in ES6 you can do it in one line:

  • 2017 edit: I use || to reduce code size and make it more readable.

var a=[7,1,7,2,2,7,3,3,3,7,,7,7,7];
alert(JSON.stringify(

a.reduce((r,k)=>{r[k]=1+r[k]||1;return r},{})

));

It can be used to count characters:

var s="ABRACADABRA";
alert(JSON.stringify(

s.split('').reduce((a, c)=>{a[c]++?0:a[c]=1;return a},{})

));
Contribution answered 1/10, 2016 at 0:20 Comment(6)
It would be more readable if you used || 0: (r,k)=>{r[k]=(r[k]||0)+1;return r}Reviewer
You can do anything in one line in JavaScript.Dagostino
And why is it a bad thing, @Ry-?Contribution
Sometimes it's more clear in several lines, other is clearer in one line. Althoug it's a matter of "taste".Contribution
I mean “in ES6 you can do it in one line” applies to every answer, and you could also do this in ES5 in one line.Dagostino
It may apply or may not apply, but I don't see the point: This is not every answer, it's only one answer. And in one line with ES6 it's clearer than multiple lines, in ES5 I think it's not (if you think it is, be free to post your answer).Contribution
D
8

If you are using underscore you can go the functional route

a = ['foo', 'foo', 'bar'];

var results = _.reduce(a,function(counts,key){ counts[key]++; return counts },
                  _.object( _.map( _.uniq(a), function(key) { return [key, 0] })))

so your first array is

_.keys(results)

and the second array is

_.values(results)

most of this will default to native javascript functions if they are available

demo : http://jsfiddle.net/dAaUU/

Dixil answered 16/10, 2013 at 6:25 Comment(0)
M
7
var array = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

function countDuplicates(obj, num){
  obj[num] = (++obj[num] || 1);
  return obj;
}

var answer = array.reduce(countDuplicates, {});
// answer => {2:5, 4:1, 5:3, 9:1};

If you still want two arrays, then you could use answer like this...

var uniqueNums = Object.keys(answer);
// uniqueNums => ["2", "4", "5", "9"];

var countOfNums = Object.keys(answer).map(key => answer[key]);
// countOfNums => [5, 1, 3, 1];

Or if you want uniqueNums to be numbers

var uniqueNums = Object.keys(answer).map(key => +key);
// uniqueNums => [2, 4, 5, 9];
Maharajah answered 20/4, 2017 at 21:26 Comment(1)
es6/7 makes this all much nicer. You may also want to reduce to a Map instead, since it will avoid the typecasting that using a number as an object key (casting as string) does. const answer = array.reduce((a, e) => a.set(e, (a.get(e) || 0) + 1), new Map()) .You can get answer.keys() for the keys, and answer.values() for the values as arrays. [...answer] will give you a big array with all the key/values as 2d arrays.Witching
R
7

So here's how I'd do it with some of the newest javascript features:

First, reduce the array to a Map of the counts:

let countMap = array.reduce(
  (map, value) => {map.set(value, (map.get(value) || 0) + 1); return map}, 
  new Map()
)

By using a Map, your starting array can contain any type of object, and the counts will be correct. Without a Map, some types of objects will give you strange counts. See the Map docs for more info on the differences.

This could also be done with an object if all your values are symbols, numbers, or strings:

let countObject = array.reduce(
  (map, value) => { map[value] = (map[value] || 0) + 1; return map },
  {}
)

Or slightly fancier in a functional way without mutation, using destructuring and object spread syntax:

let countObject = array.reduce(
  (value, {[value]: count = 0, ...rest}) => ({ [value]: count + 1, ...rest }),
  {}
)

At this point, you can use the Map or object for your counts (and the map is directly iterable, unlike an object), or convert it to two arrays.

For the Map:

countMap.forEach((count, value) => console.log(`value: ${value}, count: ${count}`)

let values = countMap.keys()
let counts = countMap.values()

Or for the object:

Object
  .entries(countObject) // convert to array of [key, valueAtKey] pairs
  .forEach(([value, count]) => console.log(`value: ${value}, count: ${count}`)

let values = Object.keys(countObject)
let counts = Object.values(countObject)
Romanticize answered 4/2, 2019 at 22:41 Comment(1)
"Without a Map, some types of objects will give you strange counts" +1 for explaining how a map is better than an Object.Distil
G
6

Here's just something light and easy for the eyes...

function count(a,i){
 var result = 0;
 for(var o in a)
  if(a[o] == i)
   result++;
 return result;
}

Edit: And since you want all the occurences...

function count(a){
 var result = {};
 for(var i in a){
  if(result[a[i]] == undefined) result[a[i]] = 0;
  result[a[i]]++;
 }
 return result;
}
Gissing answered 24/4, 2013 at 23:55 Comment(0)
A
6

Solution using a map with O(n) time complexity.

var arr = [2, 2, 2, 2, 2, 4, 5, 5, 5, 9];

const countOccurrences = (arr) => {
    const map = {};
    for ( var i = 0; i < arr.length; i++ ) {
        map[arr[i]] = ~~map[arr[i]] + 1;
    }
    return map;
}

Demo: http://jsfiddle.net/simevidas/bnACW/

Achaemenid answered 7/12, 2019 at 12:23 Comment(2)
My upvote to you, this works like butter with O(n) time complexityPyatt
I timed many answers from this thread (link jsbench.me/2wkzzagdu3/1 and this was the fastest). I imagine even without the bit twiddling, it would be similarly fast, just given the plain for loop. The lodash solution is 9% slower, and without the tilde tilde and replace with (map[arr[i]] || 0) it is also 9% slower. Other answers are much slower than this one.Immunize
D
3

There is a much better and easy way that we can do this using ramda.js. Code sample here

const ary = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]; R.countBy(r=> r)(ary) countBy documentation is at documentation

Diptych answered 4/1, 2017 at 5:50 Comment(0)
G
3

I know this question is old but I realized there are too few solutions where you get the count array as asked with a minimal code so here is mine

// The initial array we want to count occurences
var initial = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];  

// The count array asked for
var count = Array.from(new Set(initial)).map(val => initial.filter(v => v === val).length);  

// Outputs [ 3, 5, 1, 1 ]

Beside you can get the set from that initial array with

var set = Array.from(new Set(initial));  

//set = [5, 2, 9, 4]  
Goosy answered 2/9, 2020 at 3:23 Comment(2)
This code is terribly inefficient as it iterates on the initial array length^2 times.Hy
Yes this code is length² time complex, that's why I insisted its aim is to provide a minimal code that solves that problem!Goosy
J
3

To return an array which is then sortable:

let array = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]
let reducedArray = array.reduce( (acc, curr, _, arr) => {
    if (acc.length == 0) acc.push({item: curr, count: 1})
    else if (acc.findIndex(f => f.item === curr ) === -1) acc.push({item: curr, count: 1})
    else ++acc[acc.findIndex(f => f.item === curr)].count
    return acc
}, []);

console.log(reducedArray.sort((a,b) => b.count - a.count ))

/*
  Output:
  [
    {
      "item": 2,
      "count": 5
    },
    {
      "item": 5,
      "count": 3
    },
    {
      "item": 9,
      "count": 1
    },
    {
      "item": 4,
      "count": 1
    }
  ]

*/
Jahdiel answered 4/5, 2022 at 21:47 Comment(0)
S
2

My solution with ramda:

const testArray = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]

const counfFrequency = R.compose(
  R.map(R.length),
  R.groupBy(R.identity),
)

counfFrequency(testArray)

Link to REPL.

Symbol answered 11/2, 2019 at 22:26 Comment(0)
D
2

Using MAP you can have 2 arrays in the output: One containing the occurrences & the other one is containing the number of occurrences.

const dataset = [2,2,4,2,6,4,7,8,5,6,7,10,10,10,15];
let values = [];
let keys = [];

var mapWithOccurences = dataset.reduce((a,c) => {
  if(a.has(c)) a.set(c,a.get(c)+1);
  else a.set(c,1);
  return a;
}, new Map())
.forEach((value, key, map) => {
  keys.push(key);
  values.push(value);
});


console.log(keys)
console.log(values)
Dialectical answered 27/3, 2019 at 11:10 Comment(0)
M
2

This question is more than 8 years old and many, many answers do not really take ES6 and its numerous advantages into account.

Perhaps is even more important to think about the consequences of our code for garbage collection/memory management whenever we create additional arrays, make double or triple copies of arrays or even convert arrays into objects. These are trivial observations for small applications but if scale is a long term objective then think about these, thoroughly.

If you just need a "counter" for specific data types and the starting point is an array (I assume you want therefore an ordered list and take advantage of the many properties and methods arrays offer), you can just simply iterate through array1 and populate array2 with the values and number of occurrences for these values found in array1.

As simple as that.

Example of simple class SimpleCounter (ES6) for Object Oriented Programming and Object Oriented Design

class SimpleCounter { 

    constructor(rawList){ // input array type
        this.rawList = rawList;
        this.finalList = [];
    }

    mapValues(){ // returns a new array

        this.rawList.forEach(value => {
            this.finalList[value] ? this.finalList[value]++ : this.finalList[value] = 1;
        });

        this.rawList = null; // remove array1 for garbage collection

        return this.finalList;

    }

}

module.exports = SimpleCounter;
Merit answered 9/7, 2019 at 11:10 Comment(1)
Sticking a function in a class for no reason doesn’t make it object-oriented, finalList has no reason to be an array, and this has no advantages over doing it properly.Dagostino
S
2

Using Lodash

const values = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
const frequency = _.map(_.groupBy(values), val => ({ value: val[0], frequency: val.length }));
console.log(frequency);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Stockstill answered 7/7, 2020 at 4:36 Comment(0)
C
2

Given the array supplied below:

const array = [ 'a', 'b', 'b', 'c', 'c', 'c' ];

You can use this simple one-liner to generate a hash map which links a key to the number of times it appears in the array:

const hash = Object.fromEntries([ ...array.reduce((map, key) => map.set(key, (map.get(key) || 0) + 1), new Map()) ]);
// { a: 1, b: 2, c: 3 }

Expanded & Explained:

// first, we use reduce to generate a map with values and the amount of times they appear
const map = array.reduce((map, key) => map.set(key, (map.get(key) || 0) + 1), new Map())

// next, we spread this map into an array
const table = [ ...map ];

// finally, we use Object.fromEntries to generate an object based on this entry table
const result = Object.fromEntries(table);

credit to @corashina for the array.reduce code

Cosec answered 27/7, 2020 at 16:40 Comment(0)
W
1

Check out the code below.

<html>
<head>
<script>
// array with values
var ar = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

var Unique = []; // we'll store a list of unique values in here
var Counts = []; // we'll store the number of occurances in here

for(var i in ar)
{
    var Index = ar[i];
    Unique[Index] = ar[i];
    if(typeof(Counts[Index])=='undefined')  
        Counts[Index]=1;
    else
        Counts[Index]++;
}

// remove empty items
Unique = Unique.filter(function(){ return true});
Counts = Counts.filter(function(){ return true});

alert(ar.join(','));
alert(Unique.join(','));
alert(Counts.join(','));

var a=[];

for(var i=0; i<Unique.length; i++)
{
    a.push(Unique[i] + ':' + Counts[i] + 'x');
}
alert(a.join(', '));

</script>
</head>
<body>

</body>
</html>
Whiny answered 14/4, 2011 at 18:59 Comment(0)
A
1

You can simplify this a bit by extending your arrays with a count function. It works similar to Ruby’s Array#count, if you’re familiar with it.

Array.prototype.count = function(obj){
  var count = this.length;
  if(typeof(obj) !== "undefined"){
    var array = this.slice(0), count = 0; // clone array and reset count
    for(i = 0; i < array.length; i++){
      if(array[i] == obj){ count++ }
    }
  }
  return count;
}

Usage:

let array = ['a', 'b', 'd', 'a', 'c'];
array.count('a'); // => 2
array.count('b'); // => 1
array.count('e'); // => 0
array.count(); // => 5

Gist


Edit

You can then get your first array, with each occurred item, using Array#filter:

let occurred = [];
array.filter(function(item) {
  if (!occurred.includes(item)) {
    occurred.push(item);
    return true;
  }
}); // => ["a", "b", "d", "c"]

And your second array, with the number of occurrences, using Array#count into Array#map:

occurred.map(array.count.bind(array)); // => [2, 1, 1, 1]

Alternatively, if order is irrelevant, you can just return it as a key-value pair:

let occurrences = {}
occurred.forEach(function(item) { occurrences[item] = array.count(item) });
occurences; // => {2: 5, 4: 1, 5: 3, 9: 1}
Allegraallegretto answered 2/3, 2013 at 2:1 Comment(0)
L
1
function countOcurrences(arr){
    return arr.reduce((aggregator, value, index, array) => {
      if(!aggregator[value]){
        return aggregator = {...aggregator, [value]: 1};  
      }else{
        return aggregator = {...aggregator, [value]:++aggregator[value]};
      }
    }, {})
}
Logging answered 28/8, 2018 at 20:37 Comment(1)
Extremely wasteful to copy the object every time. Creates a quadratic worst case when it could be linear.Dagostino
P
0

I was solving a similar problem on codewars and devised the following solution which worked for me.

This gives the highest count of an integer in an array and also the integer itself. I think it can be applied to string array as well.

To properly sort Strings, remove the function(a, b){return a-b} from inside the sort() portion

function mostFrequentItemCount(collection) {
    collection.sort(function(a, b){return a-b});
    var i=0;
    var ans=[];
    var int_ans=[];
    while(i<collection.length)
    {
        if(collection[i]===collection[i+1])
        {
            int_ans.push(collection[i]);
        }
        else
        {
            int_ans.push(collection[i]);
            ans.push(int_ans);
            int_ans=[];
        }
        i++;
    }

    var high_count=0;
    var high_ans;

    i=0;
    while(i<ans.length)
    {
        if(ans[i].length>high_count)
        {
            high_count=ans[i].length;
            high_ans=ans[i][0];
        }
        i++;
    }
    return high_ans;
}
Plaintiff answered 6/12, 2015 at 4:13 Comment(0)
T
0

Here is a way to count occurrences inside an array of objects. It also places the first array's contents inside a new array to sort the values so that the order in the original array is not disrupted. Then a recursive function is used to go through each element and count the quantity property of each object inside the array.

var big_array = [
  { name: "Pineapples", quantity: 3 },
  { name: "Pineapples", quantity: 1 },
  { name: "Bananas", quantity: 1 },
  { name: "Limes", quantity: 1 },
  { name: "Bananas", quantity: 1 },
  { name: "Pineapples", quantity: 2 },
  { name: "Pineapples", quantity: 1 },
  { name: "Bananas", quantity: 1 },
  { name: "Bananas", quantity: 1 },
  { name: "Bananas", quantity: 5 },
  { name: "Coconuts", quantity: 1 },
  { name: "Lemons", quantity: 2 },
  { name: "Oranges", quantity: 1 },
  { name: "Lemons", quantity: 1 },
  { name: "Limes", quantity: 1 },
  { name: "Grapefruit", quantity: 1 },
  { name: "Coconuts", quantity: 5 },
  { name: "Oranges", quantity: 6 }
];

function countThem() {
  var names_array = [];
  for (var i = 0; i < big_array.length; i++) {
    names_array.push( Object.assign({}, big_array[i]) );
  }

  function outerHolder(item_array) {
    if (item_array.length > 0) {
      var occurrences = [];
      var counter = 0;
      var bgarlen = item_array.length;
      item_array.sort(function(a, b) { return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0); });

      function recursiveCounter() {
        occurrences.push(item_array[0]);
        item_array.splice(0, 1);
        var last_occurrence_element = occurrences.length - 1;
        var last_occurrence_entry = occurrences[last_occurrence_element].name;
        var occur_counter = 0;
        var quantity_counter = 0;
        for (var i = 0; i < occurrences.length; i++) {
          if (occurrences[i].name === last_occurrence_entry) {
            occur_counter = occur_counter + 1;
            if (occur_counter === 1) {
              quantity_counter = occurrences[i].quantity;
            } else {
              quantity_counter = quantity_counter + occurrences[i].quantity;
            }
          }
        }

        if (occur_counter > 1) {
          var current_match = occurrences.length - 2;
          occurrences[current_match].quantity = quantity_counter;
          occurrences.splice(last_occurrence_element, 1);
        }

        counter = counter + 1;

        if (counter < bgarlen) {
          recursiveCounter();
        }
      }

      recursiveCounter();

      return occurrences;
    }
  }
  alert(JSON.stringify(outerHolder(names_array)));
}
Taskwork answered 3/7, 2017 at 3:34 Comment(0)
W
0

Its easy with filter

In this example we simply assign count, the length of the array filtered by the key you're looking for

let array = [{name: "steve", age: 22}, {name: "bob", age: 30}]

let count = array.filter(obj => obj.name === obj.name).length

console.log(count)

more on JS Filiters here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Willis answered 2/12, 2018 at 5:29 Comment(0)
G
0
var aa = [1,3,5,7,3,2,4,6,8,1,3,5,5,2,0,6,5,9,6,3,5,2,5,6,8];
var newArray = {};
for(var element of aa){
  if(typeof newArray[element] === 'undefined' || newArray[element] === null){
    newArray[element] = 1;
  }else{
    newArray[element] +=1;
  }
}

for ( var element in newArray){
  console.log( element +" -> "+ newArray[element]);
}
Gora answered 20/6, 2019 at 10:31 Comment(0)
S
0

It seems like the questions specifically asks to have two resulting arrays, which I haven't seen, so here's my solution:

const theArray = [1, 3425, 56, 7, 9, 5, 4, 3425, 7, 7, 7];

const uniqueVals = [...new Set(theArray)];
const countPerUniqueValArray = uniqueVals.map(uv => theArray.filter(i => i === uv).length);

console.log(uniqueVals);
console.log(countPerUniqueValArray);

// Expect:
// [1, 3425, 56, 7, 9, 5, 4]
// [1, 2, 1, 4, 1, 1, 1]
Stickup answered 6/6, 2021 at 14:1 Comment(0)
H
0

const data = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4] 
function countAndSort(arr) { 
    return Object.entries(arr.reduce((prev, curr) => (prev[curr] = ++prev[curr] || 1, prev), {})).sort((a,b) => b[1]-a[1])
} 
console.log(countAndSort(data))
Harv answered 22/7, 2021 at 13:14 Comment(1)
The return format seems pretty awkward -- a list of lists where each inner list is a key and a count.Distil
H
0

let numberArray = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

We'll use Reduce method to count the occurrence from array

const uniqueIntArray = numberArray.reduce((x: any, y: any) => ((x[y] = (x[y] || 0) + 1 ), x), {})

console.log('uniqueIntArray ', uniqueIntArray); { "2": 5, "4": 1, "5": 3, "9": 1 }

Heulandite answered 23/5, 2023 at 10:16 Comment(0)
J
0

<!DOCTYPE html>
<html>
<body>

<script>
const findOccurance = (arr) => {
let resultObj={};
let sortedArr = arr.sort();
let uniqueArrVal = [...new Set(sortedArr)]

uniqueArrVal.forEach(e=> resultObj[e]=(sortedArr.lastIndexOf(e)-sortedArr.indexOf(e))+1)
return resultObj;
}

console.log(findOccurance (['a','b','a']))
console.log(findOccurance ([1,98,5,1,6,'a','f','a',98]))
</script>

</body>
</html> 

If you want the output with key as the number and value as the count (i.e) get an obj returned like this {a:1, b:2} for the input ['a','b','a'], then here is the code

    const findOccurance = (arr) => {
    let resultObj={};
    let sortedArr = arr.sort();
    let uniqueArrVal = [...new Set(sortedArr)]
    
    uniqueArrVal.forEach(e=> resultObj[e]=(sortedArr.lastIndexOf(e)-sortedArr.indexOf(e))+1)
    return resultObj;
    }
console.log(findOccurance (['a','b','a']))
console.log(findOccurance ([1,98,5,1,6,'a','f','a',98]))
Jorge answered 15/8, 2023 at 11:23 Comment(0)
S
-1

Try this:

Array.prototype.getItemCount = function(item) {
    var counts = {};
    for(var i = 0; i< this.length; i++) {
        var num = this[i];
        counts[num] = counts[num] ? counts[num]+1 : 1;
    }
    return counts[item] || 0;
}
Stymie answered 2/8, 2012 at 12:1 Comment(1)
getItemCount doesn't need to create an array if it only counts a single item. Either make it getItemCounts and return counts, or only count occurrences of item.Distil
A
-1

Here's a classic old school method for counting arrays.

var arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
var counted = [], count = [];
var i = 0, j = 0, k = 0;
while (k < arr.length) {
    if (counted.indexOf(arr[k]) < 0) {
        counted[i] = arr[k];
        count[i] = 0;
        for (j = 0; j < arr.length; j++) {
            if (counted[i] == arr[j]) {
                count[i]++;
            }
        }
        i++;
    } else {
        k++;
    }
}

You can sort it first if you want an alphabetical result, but if you want to preserve the order in which the data was entered then give this a try. Nested loops may be a bit slower than some of the other methods on this page.

Assuntaassur answered 3/5, 2016 at 6:56 Comment(0)
B
-1
const arr = [2, 2, 5, 2, 2, 2, 4, 5, 5, 9]
const arrCount: { value: number; count: number }[] = []

arr.sort()

arr.map((ele) => {
     const existingCount = arrCount.find((existingEntry) => {
         return existingEntry.value === ele
     })

     if (!existingCount) arrCount.push({ value: ele, count: 1 })
     else existingCount.count++
})

console.log(arrCount)
Bare answered 3/3, 2023 at 13:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.