Sort Array by Element Frequency JavaScript - javascript

I want to sort an array by element frequency. My code works for arrays of strings, but not for arrays of numbers:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
frequencySort(['a','b','b','b','c','c'])) returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
frequencySort([4, 6, 2, 2, 6, 4, 4, 4]) returns [ 4, 4, 4, 4, 6, 2, 2, 6 ]

The only reason your letters worked is because you didn't have the same number of any two letters, where in your numbers, you have 2 of both 2 and 6.
Here's your snippet, but with 2 a's and 2 c's. You'll see it's out of order just like the numbers.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
You need a way to sort instances that have the same number of occurrences. I adapted your forEach loop to give the last index of each letter to your b object and then changed your sort to use that index in case the number of occurrences is the same.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach((i,index) => d[i] = {
num: countOccurrences(arr,i),
i: index
});
arr.sort(function(a,b){
let diff = d[b].num - d[a].num;
if(diff == 0)
diff = d[b].i - d[a].i;
return diff;
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4]));

It has nothing to do with the elements being letters or numbers. In you letters array, each letter has unique occurence count (3, 2, 1), therefore they are sorted the way you want to.
However, in your numbers array, "2" and "6" both occur 2 times each. Therefore, your sort callback function returns 0 for them, and they are treated as equal order by the sort function.

In your array of numbers you have the same amount of the number 2 as 6 and your sorting function doesn't care about the actual values it just cares about their counts. So in your example 2 and 6 both have the same priority.
You want to adjust your sorting function to compare values of elements if they have the same amount of occurrences.
You'll need to implement separate comparisons for all the data types you want to accept and decide if you want ascending/descending order.
Here is a basic example for number and string elements:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
const r = d[b] - d[a]
if (r != 0) return r
switch (typeof d[a]) {
case 'number': return a - b
case 'string': return a.localeCompare(b)
default: return 0
}
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c'])) // returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4])) // returns [ 4, 4, 4, 4, 2, 2, 6, 6 ]

A possible approach would first collect all equal array items within an item specific group array by a reduce task ...
console.log(
"grouped ['a','b','b','b','c','c'] ...",
['a','b','b','b','c','c'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
console.log(
"grouped [4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'] ...",
[4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The final computation then has to transform ... via Object.values ... the temporary result (as shown above) into an array of arrays of equal items where the former gets 1stly sorted by each array's length (indicates the items frequency) and 2ndly, for arrays of equal length', by a locale compare of each array's first item. The final result is the sorted array's flatted version ...
function sortItemsByFrequency(arr) {
const groupedItems = arr.reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {});
return Object
.values(groupedItems)
.sort((a, b) =>
// - sort by frequency first indicated by an
// array's length.
// - the higher frequency count wins.
b.length - a.length ||
// in case of equal frequency counts do a
// locale compare of both array's first items.
b[0].toLocaleString().localeCompare(a[0].toLocaleString())
)
.flat();
}
console.log(
"sortItemsByFrequency(['a','b','b','b','c','c']) ...",
sortItemsByFrequency(['a','b','b','b','c','c'])
);
console.log(
"sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2']) ...",
sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

sort first based on frequency of characters in desc order,if freq is same, then sort alphabetically in asc order.
const str = 'zzzzvnttteeeqqaao';
const frequencySort = (str = '') => {
let map = {}
for (const letter of str) {
map[letter] = (map[letter] || 0) + 1;
};
let res = "";
let sorted = Object.keys(map).sort((a, b) => map[b] < map[a] ? -1 : 1);
//console.log(sorted);
for (let letter of sorted) {
for (let count = 0; count < map[letter]; count++) {
res += letter
}
}
return res;
};
console.log(frequencySort(str));

Related

Count values inside a specific array length

I'm searching for a nice approach for the following task: I have a game with rounds. In each round we have a number between 1-10.
This is how I'm doing it so far but I'm stuck.
gameValues.forEach(function(x) { counts[x] = (counts[x] || 0)+1; });
I want to count the double entrees in my array but I only want to watch out for the last 5 rounds.
Round 6 ( We already have 5 values ):
gameValues = [9,9,9,2,1]
result: 9:3, 2:1, 1:1,
Round 7 ( Now we have 6 values but I only want to count the first 5):
gameValues = [3,9,9,9,2,1]
result: 3:1, 9:3, 2:1,
I can't manage to only count the last 5 rounds..
Use gameValues.slice(-5) to get the last 5 values of the array, then do the same count operation :
gameValues = [3,9,9,9,2,1]
counts = {}
gameValues.slice(-5).forEach(function(x) { counts[x] = (counts[x] || 0)+1; });
console.log(counts);
// { '1': 1, '2': 1, '9': 3 }
You might want to slice the array before looping through it.
gameValues.slice(-5).forEach(function(x) { counts[x] = (counts[x] || 0)+1; });
const gameValues = [3,9,9,9,2,1]
const uniq = gameValues.splice(-5)
.map((number) => {
return {
count: 1,
number: number
}})
.reduce((a, b) => {
a[b.number] = (a[b.number] || 0) + b.count
return a
}, {})
console.log(uniq);
You can get the last 5 numbers in an array using splice and then using reduce you can get the counts of each number.
The splice() method changes the contents of an array by removing or
replacing existing elements and/or adding new elements in place. To
access part of an array without modifying it, see slice().
function countLastFive(arr) {
return arr.splice(-5).reduce((acc, curr) => {
acc[curr] = acc[curr] ? ++acc[curr] : 1;
return acc;
}, {});
}
console.log(countLastFive([9, 9, 9, 2, 1, 3, 1]));
console.log(countLastFive([9, 9, 9, 2, 1]));
console.log(countLastFive([3, 9, 9, 9, 2, 6, 4, 3, 2, 1]));
Just be sure that the slice changes the original array. So it would be better to clone the array so that It won't change the original array
function countLastFive(arr) {
return arr.splice(-5).reduce((acc, curr) => {
acc[curr] = acc[curr] ? ++acc[curr] : 1;
return acc;
}, {});
}
const arr1 = [9, 9, 1, 9, 2, 1];
const arr2 = [9, 9, 1, 9, 2, 1];
console.log("Before -> arr1 => ", arr1);
console.log(countLastFive(arr1));
console.log("After -> arr1 => ", arr1);
console.log("Before -> arr2 => ", arr2);
console.log(countLastFive([...arr2]));
console.log("After -> arr2 => ", arr2);

Grouping items with more that one indexes into sub arrays

Trying to create a function that groups repeated items in an array into sub arrays, and also grouping strings (should there be any) into another subarray.
I tried using the findIndex method to define i and then iterate it and push in into an [], using reduce
let roughArray = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];
function sortArray() {
roughArray.map(num => {
if (num[i] > 1) {
roughArray.reduce((acc, num) => {
return acc.concat(num)
}, [])
}
})
sortArray()
I also tried:
const cleanArray = roughArray.reduce((acc, num) => {
let i = acc.findIndex(num);
if (i) {
return acc.concat(num);
}
}, [])
cleanArray();
I expect this in case of only numbers
[[1,1,1,1],[2,2,2], 4,5,10,[20,20], 391, 392,591]
And this in case of some included strings:
[[1,2], ["2", "3"]]
let roughArray = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20];
let results = {}
roughArray.map( num => {
if(results[num])
results[num].push(num)
else
results[num] = [num]
})
let bigArray = []
bigArray = Object.values(results)
let final_result = []
bigArray.map(array => {
if(array.length == 1)
final_result.push(array[0])
else
final_result.push(array)
})
console.log(final_result)
You could declare some callbacks for the various types of grouping and get the wanted type by checking the array and take an object for the grouped values.
function sortOf(array) {
const
mixed = (r, v) => {
var key = typeof v;
r[key] = r[key] || [];
r[key].push(v);
return r;
},
numbers = (r, v) => {
if (v in r) r[v] = [].concat(r[v], v);
else r[v] = v;
return r;
};
return Object.values(array.reduce(array.some(v => typeof v === 'string')
? mixed
: numbers,
Object.create(null)
));
}
console.log(sortOf([1, '1', '2', 3]));
console.log(sortOf([5, 2, 3, 3, 4, 5, 5, 1]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First separate the strings from the rest. Sort the numbers, group them, then add the strings back in at the end.
If you want to, you can then map all the single item arrays into just single items, but that seems like it would make the output confusing.
let start = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20, '2', '3'];
let strings = start.filter(v => typeof(v) === 'string');
let notStrings = start.filter(v => typeof(v) !== 'string');
let sortedNotStrings = notStrings.sort((a,b) => a > b);
let grouped = sortedNotStrings.reduce((acc, value) =>
{
if(acc.length > 0)
{
if(acc[0][0] === value)
{
acc[0].push(value);
}
else
{
acc.unshift([value]);
}
}
else
{
acc.unshift([value]);
}
return acc;
}, []);
let sortedGrouped = grouped.sort((g1, g2) => g1[0] > g2[0]);
let withStrings = [sortedGrouped, strings];
console.log(withStrings);
let lonelySingleItems = sortedGrouped.map(arr => arr.length > 1 ? arr : arr[0]);
console.log([lonelySingleItems, strings]);
Regarding the if statement:
if(acc.length > 0)
{
if(acc[0][0] === value)
{
acc[0].push(value);
}
else
{
acc.unshift([value]);
}
}
else
{
acc.unshift([value]);
}
What I'm doing with the reduce function is passing in a default value [], so if we're at the start (i.e. the result is empty) then we put the first item in the sortedNotStrings array into the accumulating result (acc). This is what is happening in the outermost else.
If this isn't the beginning (i.e. acc is not empty) then we need to check if the value is the same as the last value added. If it is the same, put it into the array, otherwise start a new array in acc.
acc is an array of arrays, which is why [value] is being unshifted to start, rather than value.
In order to not have to access the last array of acc, I'm using unshift to put things on the front of the array. This is just to make the code look cleaner, by not using of acc[acc.length-1]. On the other hand you can do acc[acc.length-1].push([value]), and that means the grouped.sort is unnecessary, because the values won't be back to front.
If you have a really large array, eliminating the second sort is probably preferable to not having to type acc.length - 1.
Here I use an object literal {} as the accumulator for Array.prototype.reduce, in case of strings in the array I used str as the key of the object {} accumulator and added the strings as the value. So if a string is encountered the accumulator will be {str: "23"}.
In case of numbers I checked if the value is repeated or not, if repeated I created an array and added the new duplicate number to it with the key being the number itself e.g. {1: [1,1]}
Finally when the accumulator object is constructed I just take the values part of the accumulator object using Object.values which I return:
let roughArray = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20, "23", "23", "34"];
function group(roughArray) {
return Object.values(roughArray.reduce((r, e) => {
if(r['str'] && typeof e === "string"){ r['str'] = Array.isArray(r['str']) ? [...r['str']].concat(e): [r['str'], e]; }
else if(typeof e === "string"){r['str'] = [e]}
else if(r[e]){r[e] = Array.isArray(r[e]) ? [...r[e]].concat(e): [r[e], e];}
else{ r[e] = e}
return r;
}, {}));
}
console.log(group(roughArray));
Note: Array.isArray(r['str']) checks whether the value of the str key is an array if so I can use the es6 spread operator ... to get the old values of the array and also append the new one to the existing array.

How to check if array elements match some pattern (eg:XXXXYY)?

For example,I have array:
[1,2,3,2,2,2,1,2,3]
, which matches the pattern XXXXYY because it has (at least) four '2' and two '1', but my question is, how to I check if the array matches such pattern? I tried:
const arr=[1,2,3,2,2,2,1,3,2];
const pattern=[4,2];
let m=new Map();
for(const num of arr){
if(!m[num]){
m[num]=0;
}
m[num]++;
}
let i=0;
let isMatch=true;
for(const key in m){
if(m[key]<pattern[i]){
isMatch=false;
}
i++;
}
console.log(isMatch);
But isMatch is false. Is there any simpler method to do this?
You could count the values and then take the sorted counts and check against the sorted pattern.
var DESC = (a, b) => b - a,
array = [1, 2, 3, 2, 2, 2, 1, 3, 2],
pattern = [4, 2],
count = Array
.from(array.reduce((m, v) => m.set(v, (m.get(v) || 0) + 1), new Map).values())
.sort(DESC),
check = pattern
.sort(DESC)
.every((c, i) => count[i] >= c);
console.log(check);
console.log(count);

How to sort an array of odd numbers in ascending order, but keep even numbers at their position?

I want to sort only odd numbers without moving even numbers. For example, when I write :
sortArray([5, 3, 2, 8, 1, 4])
The expected result is :
[1, 3, 2, 8, 5, 4]
I am new to JavaScript and I came across a challenge on the Internet that has me perplexed. I normally wouldn't post asking for a solution on the Internet, BUT I have tried for hours and I would like to learn this concept in JavaScript.
The challenge states :
You have an array of numbers.
Your task is to sort ascending odd numbers but even numbers must be on their places.
Zero isn't an odd number and you don't need to move it. If you have an empty array, you need to return it.
Here is my code so far, please take it easy on me I am in the beginning stages of programming.
function sortArray(array) {
let oddNums = [];
for(let i = 0; i < array.length; i++) {
if(array[i] % 2 !== 0) {
oddNums.push(array[i]);
}
}
oddNums = oddNums.sort((a,b)=> a-b);
array.concat(oddNums);
array = array.sort((a,b) => a-b);
return array;
}
You could take a helper array for the odd indices and another for the odd numbers, sort them and apply them back on the previously stored indices of the original array.
var array = [5, 3, 2, 8, 1, 4],
indices = [];
array
.filter((v, i) => v % 2 && indices.push(i))
.sort((a, b) => a - b)
.forEach((v, i) => array[indices[i]] = v);
console.log(array);
Here's a solution using mostly the built-in array methods. Get a list of just the odds, sort it, then map through the original, replacing each item with the first sorted odd if the item is odd, or itself if even:
const array = [5, 3, 2, 8, 1, 4] // to: [1, 3, 2, 8, 5, 4]
function sortOddsOnly(arr) {
const odds = arr
.filter(x => x%2)
.sort((a, b) => a - b);
return arr
.map(x => x%2 ? odds.shift() : x);
}
console.log(sortOddsOnly(array));
I have a solution like this.
Build a sorted odd number array 1st, and then fill the rest of even numbers in order:
const arr = [5, 3, 2, 8, 1, 4];
const odd = arr.filter(i => i%2 !== 0).sort();
let i = 0,
result = [];
arr.forEach(e => {
if (e%2 === 0) {
result.push(e)
} else {
result.push(odd[i]);
i++;
}
});
console.log(result);
just do:
arr.sort((a, b) => a%2 && b%2 ? a - b : 0)
If that works depends on the sort algorithm your browser uses.
A browserindependent version:
for(const [i1, v1] of arr.entries())
for(const [i2, v2] of arr.entries())
if( v1%2 && v2%2 && (i1 < i2) === (v1 > v2))
([arr[i1], arr[i2]] = [v2, v1]);
One of the possible solutions is this. What I have done is created new array odd(array with odd position in original array using Array.prototype.filter) and then sort that array using Array.prototype.sort. Then using Array.prototype.map change value of all odd element of original array with odd array.
x1=[5, 3, 2, 8, 1, 4];
function sortArray(array) {
var odd = array.filter((x,i) => (i+1) % 2 ).sort((a,b) => a > b); //sort odd position and store that in new array
return array.map((x,i) => (i+1) % 2 ? odd.shift() : x ); //if i is odd then replace it with element from
//odd array otherwise keep the element as it is
}
console.log(sortArray(x1));
Here is a possible solution using a slightly customized selection sort :
var xs = [5, 3, 2, 8, 1, 4];
console.log(sortOddsOnly(xs));
function sortOddsOnly (xs) {
var n = xs.length;
for (var i = 0; i < n - 1; i++) {
if (xs[i] % 2 === 1) {
for (var j = i + 1; j < n; j++) {
if (xs[j] % 2 === 1) {
if (xs[i] > xs[j]) {
var min = xs[j];
xs[j] = xs[i];
xs[i] = min;
}
}
}
}
}
return xs;
}
The first two if guarantee that we swap only odd numbers (x % 2 === 1 means "x is odd").
def sort_array(source_array):
b = sorted([n for n in source_array if n % 2 != 0])
c = -1
d = []
for i in source_array:
c = c+1
if i % 2 != 0 :
d.append(c)
for x in range (len(d)):
z = d[x]
source_array[z] = b[x]
return source_array

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

Categories

Resources