Reputation: 13383
I'm looking for an elegant way of determining which element has the highest occurrence (mode) in a JavaScript array.
For example, in
['pear', 'apple', 'orange', 'apple']
the 'apple'
element is the most frequent one.
Upvotes: 128
Views: 193076
Reputation: 11
Wanting to add my solution since I wanted to find a compromise between readability (e.g. declarative, modern ES functions) and performance. This solution is O(2n), though real world performance depends on the number of unique keys.
const mode = (values) => {
if (values.length === 0) {
return undefined;
}
const { counts, maxCount } = values.reduce(
({ counts, maxCount }, value) => {
const count = (counts.get(value) ?? 0) + 1;
counts.set(value, count);
return { counts, maxCount: Math.max(maxCount, count) };
},
{ counts: new Map(), maxCount: 0 },
);
return (Array.from(counts).find(([, count]) => count === maxCount))[0];
}
If you benchmark it, you'll see it's about 50% slower than the for loop, but faster than chaining .reduce and .filter.
Upvotes: 0
Reputation: 22769
The best performance could give Map
in many cases (remember counts in it):
const arr = ['pear', 'apple', 'orange', 'apple'];
const result = arr.reduce((r, item, curr) => (
(curr = r.map.get(item)) && ++curr.count || r.map.set(item, curr = { item, count: 1 }),
r.max.count < curr.count && (r.max = curr), r
), { map: new Map, max: { item: null, count: 0 } }).max.item;
console.log(result);
` Chrome/121
-------------------------------------------------------------------------------------------------
> n=4 | n=40 | n=400 | n=4000
Alexander 9.09x x10m 589 | 48.04x x1m 319 | 1.00x x100k 272 | 1.00x x10k 287
Matthew Flaschen 9.88x x10m 640 | 116.87x x1m 776 | 2.84x x100k 773 | 2.77x x10k 796
davidsharp 23.92x x1m 155 | 1368.98x x100k 909 | 241.54x x1k 657 | 7456.45x x1 214
Emissary 1.00x x100m 648 | 1.00x x100m 664 | 44.49x x1k 121 | 16933.80x x1 486
-------------------------------------------------------------------------------------------------
https://github.com/silentmantra/benchmark `
const $chunk = ['pear', 'apple', 'orange', 'apple'];
const $input = [];
// @benchmark davidsharp
$input.reduce(
(a,b,i,arr)=>
(arr.filter(v=>v===a).length>=arr.filter(v=>v===b).length?a:b),
null)
// @benchmark Matthew Flaschen
function mode(array)
{
if(array.length == 0)
return null;
var modeMap = {};
var maxEl = array[0], maxCount = 1;
for(var i = 0; i < array.length; i++)
{
var el = array[i];
if(modeMap[el] == null)
modeMap[el] = 1;
else
modeMap[el]++;
if(modeMap[el] > maxCount)
{
maxEl = el;
maxCount = modeMap[el];
}
}
return maxEl;
}
// @run
mode($input);
// @benchmark Emissary
$input.sort((a,b) =>
$input.filter(v => v===a).length
- $input.filter(v => v===b).length
).pop();
// @benchmark Alexander
$input.reduce((r, item, curr) => (
(curr = r.map.get(item)) && ++curr.count || r.map.set(item, curr = { item, count: 1 }),
r.max.count < curr.count && (r.max = curr), r
), { map: new Map, max: { item: null, count: 0 } }).max.item;
/*@end*/eval(atob('e2xldCBlPWRvY3VtZW50LmJvZHkucXVlcnlTZWxlY3Rvcigic2NyaXB0Iik7aWYoIWUubWF0Y2hlcygiW2JlbmNobWFya10iKSl7bGV0IHQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7dC5zcmM9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9naC9zaWxlbnRtYW50cmEvYmVuY2htYXJrL2xvYWRlci5qcyIsdC5kZWZlcj0hMCxkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHQpfX0='));
Upvotes: 2
Reputation: 298
I'm surprised to see that no solution offers a 1 or 2-line version.
function getMode(values) {
const counters = values.reduce((acc, val) => ({ ...acc, [val]: (acc[val] || 0) + 1 }), {});
const mode = Object.keys(counters).reduce((a, b) => counters[a] > counters[b] ? a : b);
return mode;
}
First line creates a dictionary counters
like this :
{
"pear": 1,
"apple": 2,
"orange": 1,
}
And second line returns mode
, the key with the biggest number of occurrences.
"apple"
Upvotes: 0
Reputation: 19
Here is my solution :-
function frequent(number){
var count = 0;
var sortedNumber = number.sort();
var start = number[0], item;
for(var i = 0 ; i < sortedNumber.length; i++){
if(start === sortedNumber[i] || sortedNumber[i] === sortedNumber[i+1]){
item = sortedNumber[i]
}
}
return item
}
console.log( frequent(['pear', 'apple', 'orange', 'apple','pear', 'apple', 'orange', 'apple']))
Upvotes: 1
Reputation: 51
// works for all types of data within an array
function findMostRepeated(arr) {
const itemFrequencyMap = new Map()
for (let i = 0; i < arr.length; i++) {
if (itemFrequencyMap.has(arr[i])) {
itemFrequencyMap.set(arr[i], itemFrequencyMap.get(arr[i]) + 1)
} else {
itemFrequencyMap.set(arr[i], 1)
}
}
// console.log(itemFrequencyMap)
return Array.from(itemFrequencyMap).reduce((item1, item2) =>
item1[1] > item2[1] ? item1 : item2
)[0]
}
console.log(findMostRepeated(["pear", "apple", "orange", "apple"]))
Upvotes: 0
Reputation: 916
I saw that a lot of answers had a lot of recursions through the array, which I wanted to avoid. In addition to this, I wanted to have Typescript checking the result and inferring the right type.
So this is my version:
function findhighestOccurence<Type extends string | number>(myArray: Type[]) {
const countOccorrencies = myArray.reduce<{ [key in Type]: number }>(
(acc, curr) => ({ ...acc, [curr]: acc[curr] ? acc[curr] + 1 : 1 }),
{} as { [K in Type]: number }
)
return (Object.entries(countOccorrencies) as [Type, number][]).reduce<{
values: Type[] // there might be multiple "highest occurences" values
occurrences: number // how many times it has/they have occurred
}>(
(acc, [value, occurrences]) => {
// new highest occurrence
if (occurrences > acc.occurrences)
return {
...acc,
occurrences: occurrences,
values: [value],
}
// new value with same highest amount of occurrences
else if (occurrences === acc.occurrences)
return { ...acc, values: [...acc.values, value] }
return acc
},
{ values: [], occurrences: 0 }
)
}
Succint version:
function findhighestOccurenceShort<T extends string | number>(myArray: T[]) {
return (
Object.entries(
myArray.reduce<{ [key in T]: number }>(
(acc, cur) => ({ ...acc, [cur]: acc[cur] ? acc[cur] + 1 : 1 }),
{} as { [K in T]: number }
)
) as [T, number][]
).reduce<{ val: T[]; occ: number }>(
(acc, [val, occ]) =>
occ > acc.occ
? { occ, val: [val] }
: occ === acc.occ
? { occ, val: [...acc.val, val] }
: acc,
{ val: [], occ: 0 }
)
}
Upvotes: 0
Reputation: 9848
This solution has O(n)
complexity:
function findhighestOccurenceAndNum(a) {
let obj = {};
let maxNum, maxVal;
for (let v of a) {
obj[v] = ++obj[v] || 1;
if (maxVal === undefined || obj[v] > maxVal) {
maxNum = v;
maxVal = obj[v];
}
}
console.log(maxNum + ' has max value = ' + maxVal);
}
findhighestOccurenceAndNum(['pear', 'apple', 'orange', 'apple']);
Upvotes: 6
Reputation: 652
const data = ['x','y','x','z',5,2,4,5,2,3,2,'x', { x: 1 }, (x) => x];
function getModeData(data) {
return data.reduce((a,c) => {
if(typeof a[c] === "undefined") {
a[c] = 1;
} else {
a[c]++;
}
if(
typeof a.mode === "undefined" ||
(typeof a.mode !== "undefined") && a.mode.occurrences < a[c]
) {
a.mode = {
elem: c,
occurrences: a[c]
}
}
return a;
}, { mode: undefined });
}
const { mode: { elem, occurrences }, ...totals } = getModeData(data);
console.log(`The mode is ${elem} with ${occurrences} occurrences`);
console.log('The totals are:');
console.log(totals)
Upvotes: 0
Reputation: 224905
Here’s the modern version using built-in maps (so it works on more than things that can be converted to unique strings):
'use strict';
const histogram = iterable => {
const result = new Map();
for (const x of iterable) {
result.set(x, (result.get(x) || 0) + 1);
}
return result;
};
const mostCommon = iterable => {
let maxCount = 0;
let maxKey;
for (const [key, count] of histogram(iterable)) {
if (count > maxCount) {
maxCount = count;
maxKey = key;
}
}
return maxKey;
};
console.log(mostCommon(['pear', 'apple', 'orange', 'apple']));
Upvotes: 5
Reputation: 350
const frequence = (array) =>
array.reduce(
(acc, item) =>
array.filter((v) => v === acc).length >=
array.filter((v) => v === item).length
? acc
: item,
null
);
frequence([1, 1, 2])
Upvotes: 2
Reputation: 190
function getData(arr){
let obj = {}
let maxElementCount = 0
let maxEle = ''
for(let i = 0 ;i<arr.length;i++){
if(!obj[arr[i]]){
obj[arr[i]] = 1
}else{
obj[arr[i]] += 1
if(maxElementCount < obj[arr[i]]){
maxElementCount = obj[arr[i]]
maxEle = arr[i]
}
}
}
console.log(maxElementCount, maxEle)
return obj
}
You can use this simple method to get max count of element
Upvotes: 0
Reputation: 20088
Here is my solution :-
const arr = [
2, 1, 10, 7, 10, 3, 10, 8, 7, 3, 10, 5, 4, 6, 7, 9, 2, 2, 2, 6, 3, 7, 6, 9, 8,
9, 10, 8, 8, 8, 4, 1, 9, 3, 4, 5, 8, 1, 9, 3, 2, 8, 1, 9, 6, 3, 9, 2, 3, 5, 3,
2, 7, 2, 5, 4, 5, 5, 8, 4, 6, 3, 9, 2, 3, 3, 10, 3, 3, 1, 4, 5, 4, 1, 5, 9, 6,
2, 3, 10, 9, 4, 3, 4, 5, 7, 2, 7, 2, 9, 8, 1, 8, 3, 3, 3, 3, 1, 1, 3,
];
function max(arr) {
let newObj = {};
arr.forEach((d, i) => {
if (newObj[d] != undefined) {
++newObj[d];
} else {
newObj[d] = 0;
}
});
let nwres = {};
for (let maxItem in newObj) {
if (newObj[maxItem] == Math.max(...Object.values(newObj))) {
nwres[maxItem] = newObj[maxItem];
}
}
return nwres;
}
console.log(max(arr));
Upvotes: 1
Reputation: 3714
//const arr = [1, 2, 4, 3, 5, 1, 2, 3, 3];
const arr = ['pear', 'apple', 'orange', 'apple'];
// init max occurance element
let maxOcc = {'element': null, occured: 0};
// to find occurances
const res = arr.reduce((acc, el) => {
acc[el] = acc[el] ? acc[el]+1 : 1;
if(acc[el]> maxOcc.occured){
maxOcc = { 'element': el, occured: acc[el] };
}
return acc;
}, {});
console.log(maxOcc);
Upvotes: 0
Reputation: 134
Easy solution !
function mostFrequentElement(arr) {
let res = [];
for (let x of arr) {
let count = 0;
for (let i of arr) {
if (i == x) {
count++;
}
}
res.push(count);
}
return arr[res.indexOf(Math.max(...res))];
}
array = [13 , 2 , 1 , 2 , 10 , 2 , 1 , 1 , 2 , 2];
let frequentElement = mostFrequentElement(array);
console.log(`The frequent element in ${array} is ${frequentElement}`);
Loop on all element and collect the Count of each element in the array that is the idea of the solution
Upvotes: 2
Reputation: 3038
This solution can return multiple elements of an array in case of a tie. For example, an array
arr = [ 3, 4, 3, 6, 4, ];
has two mode values: 3
and 6
.
Here is the solution.
function find_mode(arr) {
var max = 0;
var maxarr = [];
var counter = [];
var maxarr = [];
arr.forEach(function(){
counter.push(0);
});
for(var i = 0;i<arr.length;i++){
for(var j=0;j<arr.length;j++){
if(arr[i]==arr[j])counter[i]++;
}
}
max=this.arrayMax(counter);
for(var i = 0;i<arr.length;i++){
if(counter[i]==max)maxarr.push(arr[i]);
}
var unique = maxarr.filter( this.onlyUnique );
return unique;
};
function arrayMax(arr) {
var len = arr.length, max = -Infinity;
while (len--) {
if (arr[len] > max) {
max = arr[len];
}
}
return max;
};
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
Upvotes: 2
Reputation: 297
Here is my way to do it so just using .filter
.
var arr = ['pear', 'apple', 'orange', 'apple'];
function dup(arrr) {
let max = { item: 0, count: 0 };
for (let i = 0; i < arrr.length; i++) {
let arrOccurences = arrr.filter(item => { return item === arrr[i] }).length;
if (arrOccurences > max.count) {
max = { item: arrr[i], count: arrr.filter(item => { return item === arrr[i] }).length };
}
}
return max.item;
}
console.log(dup(arr));
Upvotes: 1
Reputation: 61
I came up with a shorter solution, but it's using lodash. Works with any data, not just strings. For objects can be used:
const mostFrequent = _.maxBy(Object.values(_.groupBy(inputArr, el => el.someUniqueProp)), arr => arr.length)[0];
This is for strings:
const mostFrequent = _.maxBy(Object.values(_.groupBy(inputArr, el => el)), arr => arr.length)[0];
Just grouping data under a certain criteria, then finding the largest group.
Upvotes: 1
Reputation: 1951
There are a lot of answers already but just want to share with you what I came up with :) Can't say this solution counts on any edge case but anyway )
const getMostFrequentElement = ( arr ) => {
const counterSymbolKey = 'counter'
const mostFrequentSymbolKey = 'mostFrequentKey'
const result = arr.reduce( ( acc, cur ) => {
acc[ cur ] = acc[ cur ] ? acc[ cur ] + 1 : 1
if ( acc[ cur ] > acc[ Symbol.for( counterSymbolKey ) ] ) {
acc[ Symbol.for( mostFrequentSymbolKey ) ] = cur
acc[ Symbol.for( counterSymbolKey ) ] = acc[ cur ]
}
return acc
}, {
[ Symbol.for( mostFrequentSymbolKey ) ]: null,
[ Symbol.for( counterSymbolKey ) ]: 0
} )
return result[ Symbol.for( mostFrequentSymbolKey ) ]
}
Hope it will be helpful for someone )
Upvotes: 0
Reputation: 1458
Here is another ES6 way of doing it with O(n) complexity
const result = Object.entries(
['pear', 'apple', 'orange', 'apple'].reduce((previous, current) => {
if (previous[current] === undefined) previous[current] = 1;
else previous[current]++;
return previous;
}, {})).reduce((previous, current) => (current[1] >= previous[1] ? current : previous))[0];
console.log("Max value : " + result);
Upvotes: 3
Reputation: 3475
Can try :
var arr = [10,3,4,5,3,4,3,8,3,6,3,5,1];
var temp = {};
for(let i=0;i<arr.length;i++){
if(temp[arr[i]]==undefined){
temp[arr[i]]=1;
}else{
temp[arr[i]]+=1;
}
}
var max=0, maxEle;
for(const i in temp){
if(temp[i]>max){
max = temp[i];
maxEle=i;
}
}
console.log(`most occurred element is ${maxEle} and number of times is ${max}`);`
Upvotes: 0
Reputation: 581
For the sake of really easy to read, maintainable code I share this:
function getMaxOcurrences(arr = []) {
let item = arr[0];
let ocurrencesMap = {};
for (let i in arr) {
const current = arr[i];
if (ocurrencesMap[current]) ocurrencesMap[current]++;
else ocurrencesMap[current] = 1;
if (ocurrencesMap[item] < ocurrencesMap[current]) item = current;
}
return {
item: item,
ocurrences: ocurrencesMap[item]
};
}
Hope it helps someone ;)!
Upvotes: 4
Reputation: 1231
As per George Jempty's
request to have the algorithm account for ties, I propose a modified version of Matthew Flaschen's
algorithm.
function modeString(array) {
if (array.length == 0) return null;
var modeMap = {},
maxEl = array[0],
maxCount = 1;
for (var i = 0; i < array.length; i++) {
var el = array[i];
if (modeMap[el] == null) modeMap[el] = 1;
else modeMap[el]++;
if (modeMap[el] > maxCount) {
maxEl = el;
maxCount = modeMap[el];
} else if (modeMap[el] == maxCount) {
maxEl += "&" + el;
maxCount = modeMap[el];
}
}
return maxEl;
}
This will now return a string with the mode element(s) delimited by a &
symbol. When the result is received it can be split on that &
element and you have your mode(s).
Another option would be to return an array of mode element(s) like so:
function modeArray(array) {
if (array.length == 0) return null;
var modeMap = {},
maxCount = 1,
modes = [];
for (var i = 0; i < array.length; i++) {
var el = array[i];
if (modeMap[el] == null) modeMap[el] = 1;
else modeMap[el]++;
if (modeMap[el] > maxCount) {
modes = [el];
maxCount = modeMap[el];
} else if (modeMap[el] == maxCount) {
modes.push(el);
maxCount = modeMap[el];
}
}
return modes;
}
In the above example you would then be able to handle the result of the function as an array of modes.
Upvotes: 45
Reputation: 444
Another JS solution from: https://www.w3resource.com/javascript-exercises/javascript-array-exercise-8.php
Can try this too:
let arr =['pear', 'apple', 'orange', 'apple'];
function findMostFrequent(arr) {
let mf = 1;
let m = 0;
let item;
for (let i = 0; i < arr.length; i++) {
for (let j = i; j < arr.length; j++) {
if (arr[i] == arr[j]) {
m++;
if (m > mf) {
mf = m;
item = arr[i];
}
}
}
m = 0;
}
return item;
}
findMostFrequent(arr); // apple
Upvotes: 2
Reputation: 897
// O(n)
var arr = [1, 2, 3, 2, 3, 3, 5, 6];
var duplicates = {};
max = '';
maxi = 0;
arr.forEach((el) => {
duplicates[el] = duplicates[el] + 1 || 1;
if (maxi < duplicates[el]) {
max = el;
maxi = duplicates[el];
}
});
console.log(max);
Upvotes: 2
Reputation: 3723
With ES6, you can chain the method like this:
function findMostFrequent(arr) {
return arr
.reduce((acc, cur, ind, arr) => {
if (arr.indexOf(cur) === ind) {
return [...acc, [cur, 1]];
} else {
acc[acc.indexOf(acc.find(e => e[0] === cur))] = [
cur,
acc[acc.indexOf(acc.find(e => e[0] === cur))][1] + 1
];
return acc;
}
}, [])
.sort((a, b) => b[1] - a[1])
.filter((cur, ind, arr) => cur[1] === arr[0][1])
.map(cur => cur[0]);
}
console.log(findMostFrequent(['pear', 'apple', 'orange', 'apple']));
console.log(findMostFrequent(['pear', 'apple', 'orange', 'apple', 'pear']));
If two elements have the same occurrence, it will return both of them. And it works with any type of element.
Upvotes: 1
Reputation: 1951
var cats = ['Tom','Fluffy','Tom','Bella','Chloe','Tom','Chloe'];
var counts = {};
var compare = 0;
var mostFrequent;
(function(array){
for(var i = 0, len = array.length; i < len; i++){
var word = array[i];
if(counts[word] === undefined){
counts[word] = 1;
}else{
counts[word] = counts[word] + 1;
}
if(counts[word] > compare){
compare = counts[word];
mostFrequent = cats[i];
}
}
return mostFrequent;
})(cats);
Upvotes: 0
Reputation: 1894
Here is my way. I try to group data fist.
const _ = require("underscore")
var test = [ 1, 1, 2, 1 ];
var groupResult = _.groupBy(test, (e)=> e);
The groupResult should be
{
1: [1, 1, 1]
2: [2]
}
Then find the property which has the longest array
function findMax(groupResult){
var maxArr = []
var max;
for(var item in groupResult){
if(!max) {
max = { value:item, count: groupResult[item].length } ;
maxArr.push(max);
continue;
}
if(max.count < groupResult[item].length){
maxArr = [];
max = { value:item, count: groupResult[item].length }
maxArr.push(max)
} else if(max === groupResult[item].length)
maxArr.push({ value:item, count: groupResult[item].length })
}
return maxArr;
}
The complete code looks like
const _ = require("underscore")
var test = [ 1, 1, 2, 1 ];
var groupResult= _.groupBy(test, (e)=> e);
console.log(findMax(groupResult)[0].value);
function findMax(groupResult){
var maxArr = []
var max;
for(var item in groupResult){
if(!max) {
max = { value:item, count: groupResult[item].length } ;
maxArr.push(max);
continue;
}
if(max.count < groupResult[item].length){
maxArr = [];
max = { value:item, count: groupResult[item].length }
maxArr.push(max)
} else if(max === groupResult[item].length)
maxArr.push({ value:item, count: groupResult[item].length })
}
return maxArr;
}
Upvotes: 0
Reputation: 14464
a=['pear', 'apple', 'orange', 'apple'];
b={};
max='', maxi=0;
for(let k of a) {
if(b[k]) b[k]++; else b[k]=1;
if(maxi < b[k]) { max=k; maxi=b[k] }
}
Upvotes: 15
Reputation: 896
As I'm using this function as a quiz for the interviewers, I post my solution:
const highest = arr => (arr || []).reduce( ( acc, el ) => {
acc.k[el] = acc.k[el] ? acc.k[el] + 1 : 1
acc.max = acc.max ? acc.max < acc.k[el] ? el : acc.max : el
return acc
}, { k:{} }).max
const test = [0,1,2,3,4,2,3,1,0,3,2,2,2,3,3,2]
console.log(highest(test))
Upvotes: 9
Reputation: 11
Try it too, this does not take in account browser version.
function mode(arr){
var a = [],b = 0,occurrence;
for(var i = 0; i < arr.length;i++){
if(a[arr[i]] != undefined){
a[arr[i]]++;
}else{
a[arr[i]] = 1;
}
}
for(var key in a){
if(a[key] > b){
b = a[key];
occurrence = key;
}
}
return occurrence;
}
alert(mode(['segunda','terça','terca','segunda','terça','segunda']));
Please note that this function returns latest occurence in the array when 2 or more entries appear same number of times!
Upvotes: 1