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 :)
if (arr.indexOf(value) == arr.lastIndexOf(value))
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)
arr.filter(x => x===5).length
would return 3
to indicate that there are '3' fives in the array.
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[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"]
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}
const keys = Object.keys(a);
const values = Object.values(a);
acc[curr] = (acc[curr] || 0) + 1
instead of using if/else
. You can check the answer below
[1, "1", { toString: () => "1" }]
you would give the result { 1: 3 }
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)
O(N log(N))
and the elegance gain isn't worth it
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.
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.)
dictionary
would be better. Here is my answer to another approach.
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.
filter([true, true, true, false], function(m){return m==true}).length
. This would just return 0 if no values exist.
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()])
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
}
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));
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.
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
a.filter(value => !value).length
with the new js syntax
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))
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.
;
, {
and }
. ... OK. I think with that definition of a one liner we can write Conway's Game of Life as a "oneliner".
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
}
const result = arr.reduce((acc, curr) => (acc[curr] = -~(acc[curr]), acc), {});
See stackoverflow.com/a/47546846/1659476 for an explanation.
Bitwise
of your answer looks elegant & concise as well.
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
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; }
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},{}) ));
|| 0
: (r,k)=>{r[k]=(r[k]||0)+1;return r}
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));
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/
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];
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.
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;
}
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)
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/
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
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)
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)
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);
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]
length²
time complex, that's why I insisted its aim is to provide a minimal code that solves that problem!
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>
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
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}
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;
finalList
has no reason to be an array, and this has no advantages over doing it properly.
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
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 } ] */
Success story sharing
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+.counts[num] = (counts[num] || 0) + 1
. That way you only have to writecounts[num]
twice instead of three times on that one line there.[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.