Obtener el elemento con mayor aparición en una matriz
Estoy buscando una forma elegante de determinar qué elemento tiene la mayor aparición ( modo ) en una matriz de JavaScript.
Por ejemplo, en
['pear', 'apple', 'orange', 'apple']
el 'apple'
elemento es el más frecuente.
Este es sólo el modo. Aquí tienes una solución rápida y no optimizada . Debería ser O(n).
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;
}
Ha habido algunos avances en JavaScript desde 2009; pensé en agregar otra opción. Me preocupa menos la eficiencia hasta que realmente es un problema, por lo que mi definición de código "elegante" (según lo estipulado por el OP) favorece la legibilidad, lo cual es, por supuesto, subjetivo...
function mode(arr){
return arr.sort((a,b) =>
arr.filter(v => v===a).length
- arr.filter(v => v===b).length
).pop();
}
mode(['pear', 'apple', 'orange', 'apple']); // apple
En este ejemplo particular, si dos o más elementos del conjunto tienen apariciones iguales, se devolverá el que aparece más tarde en la matriz. También vale la pena señalar que modificará su matriz original, lo que puede evitarse si lo desea con una Array.slice
llamada previa.
Editar: actualicé el ejemplo con algunas flechas gruesas de ES6 porque sucedió en 2015 y creo que se ven bonitas... Si le preocupa la compatibilidad con versiones anteriores, puede encontrar esto en el historial de revisiones .
Según la George Jempty's
solicitud de que el algoritmo tenga en cuenta los vínculos, propongo una versión modificada del Matthew Flaschen's
algoritmo.
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;
}
Esto ahora devolverá una cadena con los elementos de modo delimitados por un &
símbolo. Cuando se recibe el resultado, se puede dividir en ese &
elemento y tendrá sus modos.
Otra opción sería devolver una matriz de elementos de modo así:
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;
}
En el ejemplo anterior, podrá manejar el resultado de la función como una matriz de modos.
Según la respuesta ES6+ de Emissary , podría utilizarla Array.prototype.reduce
para hacer su comparación (en lugar de ordenar, extraer y potencialmente mutar su matriz), lo cual creo que parece bastante ingenioso.
const mode = (myArray) =>
myArray.reduce(
(a,b,i,arr)=>
(arr.filter(v=>v===a).length>=arr.filter(v=>v===b).length?a:b),
null)
El valor predeterminado es nulo, lo que no siempre le dará una respuesta veraz si nulo es una posible opción por la que está filtrando, tal vez ese podría ser un segundo argumento opcional.
La desventaja, como ocurre con otras soluciones, es que no maneja "estados de extracción", pero esto aún podría lograrse con una función de reducción un poco más complicada.