¿Cómo contar ciertos elementos en una matriz?

Resuelto Leem asked hace 13 años • 27 respuestas

Tengo una matriz:

[1, 2, 3, 5, 2, 8, 9, 2]

Me gustaría saber cuántos 2s hay en la matriz.

¿Cuál es la forma más elegante de hacerlo en JavaScript sin realizar bucles for?

Leem avatar May 25 '11 14:05 Leem
Aceptado

[ esta respuesta está un poco anticuada: lea las ediciones, la noción de 'igual' en javascript es ambigua ]

Saluda a tus amigos: mapy filtery reducey forEachy everyetc.

(Solo ocasionalmente escribo bucles for en javascript, debido a que falta el alcance a nivel de bloque, por lo que debes usar una función como cuerpo del bucle de todos modos si necesitas capturar o clonar tu índice o valor de iteración. son más eficientes en general, pero a veces es necesario un cierre).

La forma más legible:

[....].filter(x => x==2).length

(Podríamos haber escrito .filter(function(x){return x==2}).lengthen su lugar)

Lo siguiente ocupa más espacio (O(1) en lugar de O(N)), pero no estoy seguro de cuánto beneficio/penalización podría pagar en términos de tiempo (no más que un factor constante desde que visita cada elemento exactamente una vez):

[....].reduce((total,x) => (x==2 ? total+1 : total), 0)

o como amablemente señaló un comentarista:

[....].reduce((total,x) => total+(x==2), 0)

(Si necesita optimizar este fragmento de código en particular, un bucle for podría ser más rápido en algunos navegadores... puede probar cosas en jsperf.com).


Luego puedes ser elegante y convertirlo en una función prototipo:

[1, 2, 3, 5, 2, 8, 9, 2].count(2)

Como esto:

Object.defineProperties(Array.prototype, {
    count: {
        value: function(value) {
            return this.filter(x => x==value).length;
        }
    }
});

También puede incluir la antigua técnica de bucle for (ver otras respuestas) dentro de la definición de propiedad anterior (nuevamente, probablemente sería mucho más rápido).


Edición de 2017 :

Vaya, esta respuesta se ha vuelto más popular que la respuesta correcta. En realidad, simplemente use la respuesta aceptada. Si bien esta respuesta puede ser buena, los compiladores js probablemente no optimicen (o no puedan debido a especificaciones) tales casos. Entonces deberías escribir un bucle for simple:

Object.defineProperties(Array.prototype, {
    count: {
        value: function(query) {
            /* 
               Counts number of occurrences of query in array, an integer >= 0 
               Uses the javascript == notion of equality.
            */
            var count = 0;
            for(let i=0; i<this.length; i++)
                if (this[i]==query)
                    count++;
            return count;
        }
    }
});

Se podría definir una versión .countStrictEq(...)que utilizara la ===noción de igualdad. ¡La noción de igualdad puede ser importante para lo que estás haciendo! (por ejemplo [1,10,3,'10'].count(10)==2, porque números como '4'==4 en javascript... por lo tanto, llamarlo .countEqo .countNonstrictenfatizar que usa el ==operador).

Advertencia: La definición de un nombre común en el prototipo debe hacerse con cuidado. Está bien si controlas tu código, pero está mal si todos quieren declarar su propia [].countfunción, especialmente si se comportan de manera diferente. Quizás te preguntes "pero .count(query)seguramente suena bastante perfecto y canónico"... pero considera que tal vez puedas hacer algo como [].count(x=> someExpr of x). En ese caso, define funciones como countIn(query, container)(bajo myModuleName.countIn), o algo, o [].myModuleName_count().

También considere usar su propia estructura de datos de conjuntos múltiples (por ejemplo, como ' collections.Counter' de Python) para evitar tener que contar en primer lugar. Esto funciona para coincidencias exactas del formulario [].filter(x=> x==???).length(en el peor de los casos, O(N) hasta O(1) ), y la modificación acelerará las consultas del formulario [].filter(filterFunction).length(aproximadamente en un factor de #total/#duplicates ).

class Multiset extends Map {
    constructor(...args) {
        super(...args);
    }
    add(elem) {
        if (!this.has(elem))
            this.set(elem, 1);
        else
            this.set(elem, this.get(elem)+1);
    }
    remove(elem) {
        var count = this.has(elem) ? this.get(elem) : 0;
        if (count>1) {
            this.set(elem, count-1);
        } else if (count==1) {
            this.delete(elem);
        } else if (count==0)
            throw `tried to remove element ${elem} of type ${typeof elem} from Multiset, but does not exist in Multiset (count is 0 and cannot go negative)`;
            // alternatively do nothing {}
    }
}

Manifestación:

> counts = new Multiset([['a',1],['b',3]])
Map(2) {"a" => 1, "b" => 3}

> counts.add('c')
> counts
Map(3) {"a" => 1, "b" => 3, "c" => 1}

> counts.remove('a')
> counts
Map(2) {"b" => 3, "c" => 1}

> counts.remove('a')
Uncaught tried to remove element a of type string from Multiset, but does not exist in Multiset (count is 0 and cannot go negative)

nota al margen: Sin embargo, si aún desea la forma de programación funcional (o una frase desechable sin anular Array.prototype), hoy en día podría escribirlo de manera más concisa como [...].filter(x => x==2).length. Si le importa el rendimiento, tenga en cuenta que si bien este es asintóticamente el mismo rendimiento que el bucle for (tiempo O(N)), puede requerir memoria adicional O(N) (en lugar de memoria O(1)) porque casi ciertamente genere una matriz intermedia y luego cuente los elementos de esa matriz intermedia.

ninjagecko avatar May 25 '2011 07:05 ninjagecko

JavaScript moderno:

Tenga en cuenta que siempre debe utilizar triples iguales ===al realizar comparaciones en JavaScript (JS). Los triples iguales garantizan que la comparación JS se comporte como dobles iguales ==en otros lenguajes (hay una excepción, ver más abajo). La siguiente solución muestra cómo resolver esto de forma funcional, lo que garantizará que nunca tendrá out of bounds error:

// Let has local scope
let array = [1, 2, 3, 5, 2, 8, 9, 2]

// Functional filter with an Arrow function
// Filter all elements equal to 2 and return the length (count)
array.filter(x => x === 2).length  // -> 3

La siguiente función de flecha anónima (función lambda) en JavaScript:

(x) => {
   const k = 2
   return k * x
}

se puede simplificar a esta forma concisa para una sola entrada:

x => 2 * x

donde returnestá implícito.

Utilice siempre triples iguales: ===para comparar en JS, con excepción de cuando se verifica la nulidad: if (something == null) {}ya que incluye una verificación de undefined, si solo usa dobles iguales como en este caso.

Sverrisson avatar Nov 19 '2017 14:11 Sverrisson