Create Frequency Map Using Map - JavaScript - javascript

Trying to solve this Codwars Kata.
Given an array, find the duplicates in that array, and return a new array of those duplicates. The elements of the returned array should appear in the order when they first appeared as duplicates.
Examples:
[1, 2, 4, 4, 3, 3, 1, 5, 3, '5'] ==> [4, 3, 1]
[0, 1, 2, 3, 4, 5] ==> []
I have:
function duplicates(arr) {
arr.sort((value, index) => value - index);
let duplicates = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] === arr[i + 1]) {
duplicates.unshift(arr[i]);
}
}
//filter out duplicates within "duplicates"
duplicates = duplicates.filter((value, index) =>
duplicates.indexOf(value) == index);
return duplicates;
}
console.log(duplicates([1, 2, 4, 4, 3, 1, 5, '5']));
This is passing all tests except for one:
Expected: '[1, 4]', instead got: '[4, 1]'
And I'm not sure why - unfortunately it does not display the test case.
It was suggested that another way to create a frequency map, however, is with Map. How would I do this?
I tried:
function duplicates(arr) {
let map = new Map([arr]);
return map;
}
console.log(duplicates([1, 2, 4, 4, 3, 1, 5, '5']));
and this doesn't create a frequency map.
What other suggestions would you have?
NOTE - "5" and 5 should not count as the same value.
EDIT - Initially, tried to create a frequencyMap like this:
function duplicates(arr) {
let map = {};
arr.forEach((value, index) => {
if (!map[value]) {
map[value] = 0;
}
map[value] += 1;
})
return map;
}
console.log(duplicates([1, 2, 4, 4, 3, 1, 5, '5']));
But in this case, "5" and 5 are considered to be the same value. I don't know how else to check for duplicates - sorting disrupts the order in which the duplicates appear; and creating a frequencyMap count numbers and strings as the same thing.

Here's an idea that should work:
Create a Map (map keys distinguish type so 5 is a different key than "5")
Use filter to go through the items in your array. As you go through keep count of how many times you've seen an item in your Map. Return true from the filter only when the count (before you've increased it) is 1. That's the second time you've seen that item.
The filter should return your answer:
function duplicates(arr) {
let counts = new Map()
return arr.filter(n => {
let count = counts.get(n)
counts.set(n, count ? count+1 : 1)
return count === 1
})
}
console.log(duplicates([1, 2, 4, 4, 3, 1, 5, '5']));

Might not be the most efficient solution, but it solves the challenge. Note, I'm using js object to mimic a Map
function duplicates(arr) {
// TODO: return the array of duplicates from arr
const map = {};
const dup = {};
for (const val of arr) {
let key = val;
if (typeof val === 'string') {
key = `${val}_str`;
}
if (map[key]) {
dup[val] = true;
} else {
map[key] = true;
}
}
return Object.keys(dup)
.map( d => (!Number.isInteger(parseInt(d))) ? d : Number(d));
}
console.log(duplicates([1, 2, 4, 4, 3, 1, 5, '5']));

Related

Hi, I would like to get the second most repeated value within an array in javascript or if two elements are repeated the same number of times [duplicate]

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 :)
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"]
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 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)
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()])
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.
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));
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
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))
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
}
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.
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},{})
));
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/
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)
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];
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;
}
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
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]
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.
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)
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;
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>
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
}
]
*/
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>
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]};
}
}, {})
}
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}

Change value array to string, when array value is determine with specific rule [duplicate]

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 :)
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"]
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 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)
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()])
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.
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));
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
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))
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
}
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.
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},{})
));
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/
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)
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];
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;
}
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
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]
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.
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)
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;
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>
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
}
]
*/
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>
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]};
}
}, {})
}
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}

Counting ocurrencies / frequency of given value in a array inside object in an array of objects [duplicate]

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 :)
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"]
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 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)
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()])
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.
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));
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
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))
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
}
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.
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},{})
));
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/
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)
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];
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;
}
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
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]
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.
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)
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;
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>
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
}
]
*/
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>
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]};
}
}, {})
}
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}

Is there any way to modify criteria of what values to allow in an Array?

I have an array of values. I want to make a second array based on the first one with stricter criteria. For example, what I want specifically is:
arrayOne[1,1,1,1,1,2,2,2,3,3,3,3,4,4,4,4,4,5,5,5]
How would I make it so in my new array, only the values that show up 5 times are a part of the array and only show up once. Example: arrayTwo [1,4]
I'm fairly new to JavaScript and have been given an opportunity to code a decision making system for one of my business courses instead of doing the final exam. Any help you can give would be much appreciated. Thank You.
You could use a hash table, which counts each found element and then use the count for filtering and get only the fifth element as result set in a single loop.
var array = [1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5],
count = Object.create(null),
result = array.filter(v => (count[v] = (count[v] || 0) + 1) === 5);
console.log(result);
I commented the code with the steps I took:
const arrayOne = [1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5];
function method(arr, minimum) {
//Create an object containing the amount each number occurs
const occurrences = arr.reduce((o, n) => {
//If number is already in the object add 1
if (o[n]) o[n] = o[n] + 1;
//Else set its occurence to 1
else o[n] = 1;
//Return the object for the next iteration
return o;
}, {});
//Deduplicate the array be creating a Set(every elements can only occur once) and spread it back into an array
const deduplicate = [...new Set(arr)];
//Filter array to only contain elements which have the minimum of occurences
const filtered = deduplicate.filter(n => occurrences[n] >= minimum);
return filtered;
}
console.log(method(arrayOne, 5));
You can use a Map for this.
let arrayOne = [1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5];
let counterMap = new Map();
arrayOne.forEach(value => {
let valueStr = value.toString();
counterMap.set(valueStr, counterMap.has(valueStr) ? counterMap.get(valueStr) + 1 : 1);
});
let arrayTwo = [];
counterMap.forEach((value, key, map) => {
if(value >= 5) {
arrayTwo.push(key);
}
});
console.log(arrayTwo);
Not the most elegant answer, but I assume you're looking just to find all values that appear at least 5 times.
const arrayOne = [1,1,1,1,1,2,2,2,3,3,3,3,4,4,4,4,4,5,5,5]
const arrayTwo = Object.entries(arrayOne.reduce((obj, num) => {
if(!obj[num]){
obj[num] = 1
} else {
obj[num] = obj[num] + 1
}
return obj
}, {})).filter(([key, value]) => {
return value >= 5
}).map((item) => {
return parseInt(item[0])
})
console.log(arrayTwo)
const a = [1,1,1,1,1,2,2,2,3,3,3,3,4,4,4,4,4,5,5,5,5,5,5,5,5,5];
Define a function that will take an array and numOfOccurrences
const filterByOcur = (arr, numOfOccurrences) => {
// create an object to act as a counter this will let us
// iterate over the array only once
const counter = {};
const res = new Set();
for (let num of arr) {
// if it's the first time we see the num set counter.num to 1;
if (!counter[num]) counter[num] = 1;
// if counter.num is greater or equal to numOfOccurrences
// and we don't have the num in the set add it to the set
else if (++counter[num] >= numOfOccurrences && !res.has(num)) res.add(num);
}
// spread the Set into an array
return [...res];
};
console.log(
filterByOcur(a, 5)
);
There is number of ways of doing this, I will try to explain this step by step:
Array declaration
const a = [1,1,1,1,1,2,2,2,3,3,3,3,4,4,4,4,4,5,5,5]
Method to count elements in an array, we are using reducer function that as a first argument takes object where key is our value from array and has a incremental number as a value. Remeber to start reducer with empty object
const counted = a.reduce((counter, value) => {
if (counter[value]) counter[value]++
else counter[value] = 1
return counter
}, {})
Make your array unique with Set constructor
const uniq = Array.from(new Set(a))
Fire filter functions on the uniq array with a help of counted array, look how we access it:
const onlyOne = uniq.filter(val => counted[val] === 1)
const onlyFive = uniq.filter(val => counted[val] === 5)
Merge all filtered arrays into one
const final = [].concat(onlyOne, onlyFive)

Find the least duplicate items in an array

Okay so as the title says my goal is to find the least duplicate elements, given that the elements are only integers.
ex1: array = [1,1,2,2,3,3,3] result should be 1,2
ex2: array = [1,2,2,3,3,4] result should be 1,4
I could use the xor operator to find the elements that appear only once but since there might be only duplicates I cant.
I was thinking of first checking with XOR if the're any non-duplicate elements. If no proceed with fors to check for only two occurrences of the same element and so on, but that is not a good approach as its kinda slow,
any suggestions?
Another approach, using new Set() and few Array.prototype functions. If you have any questions, let me know.
var array1 = [1, 1, 2, 2, 3, 3, 3],
array2 = [1, 2, 2, 3, 3, 4];
function sort(arr) {
var filtered = [...new Set(arr)],
solution = [],
res = filtered.reduce(function(s, a) {
s.push(arr.filter(c => c == a).length);
return s;
}, []);
var minDupe = Math.min.apply([], res);
res.forEach((v, i) => v == minDupe ? solution.push(filtered[i]) : null)
console.log(solution)
}
sort(array1);
sort(array2);
Using Array#forEach instead of Array#reduce.
var array1 = [1, 1, 2, 2, 3, 3, 3],
array2 = [1, 2, 2, 3, 3, 4];
function sort(arr) {
var filtered = [...new Set(arr)],
solution = [],
res = [];
filtered.forEach(v => res.push(arr.filter(c => c == v).length));
var minDupe = Math.min.apply([], res);
res.forEach((v, i) => v == minDupe ? solution.push(filtered[i]) : null)
console.log(solution)
}
sort(array1);
sort(array2);
There may be a better or faster solution, but I would suggest to create a hash (object) with the integer as keys and the counts as values. You can create this in one loop of the array. Then, loop over the object keys keeping track of the minimum value found and add the key to the result array if it satisfies the minimum duplicate value.
Example implementation:
const counts = input.reduce((counts, num) => {
if (!counts.hasOwnProperty(num)) {
counts[num] = 1;
}
else {
counts[num]++;
}
return counts;
}, {});
let minimums = [];
let minCount = null;
for (const key in counts) {
if (!minimums.length || counts[key] < minCount) {
minimums = [+key];
minCount = counts[key];
}
else if (counts[key] === minCount) {
minimums.push(+key);
}
}
return minimums;
You can also simplify this a little bit using lodash: one operation to get the counts and another to get the minimum count and get the list of values that match that minimum count as a key:
import { countBy, invertBy, min, values } from "lodash";
const counts = countBy(input);
const minCount = min(values(counts));
return invertBy(counts)[minCount];
You could count the appearance, sort by count and delete all same max count keys. Then return the original values.
Steps:
declarate all variables, especial the hash object without any prototypes,
use the items as key got the hash table and if not set use an object with the original value and a count property,
increment count of actual hash,
get all keys from the hash table,
sort the keys in descending order of count,
get the count of the first element and store it in min,
filter all keys with min count,
get the original value for all remaining keys.
function getLeastDuplicateItems(array) {
var hash = Object.create(null), keys, min;
array.forEach(function (a) {
hash[a] = hash[a] || { value: a, count: 0 };
hash[a].count++;
});
keys = Object.keys(hash);
keys.sort(function (a, b) { return hash[a].count - hash[b].count; });
min = hash[keys[0]].count;
return keys.
filter(function (k) {
return hash[k].count === min;
}).
map(function (k) {
return hash[k].value;
});
}
var data = [
[1, 1, 2, 2, 3, 3, 3],
[1, 2, 2, 3, 3, 4],
[4, 4, 4, 6, 6, 4, 7, 8, 5, 5, 6, 3, 4, 6, 6, 7, 7, 8, 3, 3]
];
console.log(data.map(getLeastDuplicateItems));
.as-console-wrapper { max-height: 100% !important; top: 0; }
A single loop solution with a variable for min and an array for collected count.
function getLeastDuplicateItems(array) {
var hash = Object.create(null),
temp = [],
min = 1;
array.forEach(function (a) {
var p = (temp[hash[a]] || []).indexOf(a);
hash[a] = (hash[a] || 0) + 1;
temp[hash[a]] = temp[hash[a]] || [];
temp[hash[a]].push(a);
if (min > hash[a]) {
min = hash[a];
}
if (p === -1) {
return;
}
temp[hash[a] - 1].splice(p, 1);
if (min === hash[a] - 1 && temp[hash[a] - 1].length === 0) {
min++;
}
}, []);
return temp[min];
}
var data = [
[1, 1, 2, 2, 3, 3, 3],
[1, 2, 2, 3, 3, 4],
[4, 4, 4, 6, 6, 4, 7, 8, 5, 5, 6, 3, 4, 6, 6, 7, 7, 8, 3, 3],
];
console.log(data.map(getLeastDuplicateItems));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories