Jack W
Jack W

Reputation: 3921

Counting the occurrences / frequency of array elements

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 :)

Upvotes: 365

Views: 839976

Answers (30)

syed yaser mohasin
syed yaser mohasin

Reputation: 51

<!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]))

Upvotes: 0

Siddharth
Siddharth

Reputation: 31

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 }

Upvotes: 0

ajitspyd
ajitspyd

Reputation: 1304

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)

Upvotes: -1

typeof
typeof

Reputation: 5932

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"]

Upvotes: 354

Everything Digital
Everything Digital

Reputation: 191

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
    }
  ]

*/

Upvotes: 3

Penny Liu
Penny Liu

Reputation: 17468

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));

Upvotes: 11

井上智文
井上智文

Reputation: 2035

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))

Upvotes: 0

Šime Vidas
Šime Vidas

Reputation: 186033

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)

Upvotes: 117

ase
ase

Reputation: 13491

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}

Upvotes: 202

Nguyễn Văn Phong
Nguyễn Văn Phong

Reputation: 14228

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
}

Upvotes: 37

Esteban Rincon
Esteban Rincon

Reputation: 2110

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]

Upvotes: 1

KooiInc
KooiInc

Reputation: 122966

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; }

Upvotes: 12

al kaj
al kaj

Reputation: 197

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]  

Upvotes: 3

spacefluff432
spacefluff432

Reputation: 636

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

Upvotes: 2

Vinay
Vinay

Reputation: 2339

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>

Upvotes: 2

corashina
corashina

Reputation: 1887

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()])

Upvotes: 126

Sardorjon Vakkosov
Sardorjon Vakkosov

Reputation: 270

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/

Upvotes: 7

rags2riches-prog
rags2riches-prog

Reputation: 1761

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;

Upvotes: 2

Dilraj Singh
Dilraj Singh

Reputation: 1011

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]);
}

Upvotes: 0

zykadelic
zykadelic

Reputation: 1159

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}

Upvotes: 1

Melchia
Melchia

Reputation: 24314

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)

Upvotes: 2

Garrett Motzner
Garrett Motzner

Reputation: 3260

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)

Upvotes: 7

Michal
Michal

Reputation: 5226

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.

Upvotes: 2

Alex Wood
Alex Wood

Reputation: 35

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

Upvotes: 0

Jos&#233; Salgado
Jos&#233; Salgado

Reputation: 1072

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]};
      }
    }, {})
}

Upvotes: 1

radicand
radicand

Reputation: 6638

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.

Upvotes: 104

William Leung
William Leung

Reputation: 1653

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

Upvotes: 22

Emissary
Emissary

Reputation: 10148

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));

Upvotes: 62

ElDoRado1239
ElDoRado1239

Reputation: 4048

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;
}

Upvotes: 6

nate_js
nate_js

Reputation: 26

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)));
}

Upvotes: 0

Related Questions