¿Cómo contar ciertos elementos en la matriz?

162

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 bucle con forbucle?

Leem
fuente

Respuestas:

90

Muy simple:

var count = 0;
for(var i = 0; i < array.length; ++i){
    if(array[i] == 2)
        count++;
}
Thor Jacobsen
fuente
53
No, lo que quiero decir es sin bucle con "for"
Leem
9
@Leem: ¿Por qué el bucle es malo? Siempre hay bucles en algún momento. Obviamente crearía una función que oculta el bucle. Estas solicitudes de "No quiero usar la herramienta adecuada para el trabajo" nunca tuvieron mucho sentido para mí. Y podemos discutir lo que es más elegante. Por ejemplo, para mí, hacer una llamada de función por elemento solo para compararlo con un valor no es elegante.
Felix Kling
2
para risas: alerta (eval ('(' + my_array.join ('== 2) + (') + '== 2)')) jsfiddle.net/gaby_de_wilde/gujbmych
user40521
29
El OP probablemente piensa que el bucle es malo porque tiene 5 líneas de código y requiere un estado mutable. Un desarrollador que viene a leer eso luego tendrá que pasar un tiempo para verificar lo que hace y perder el enfoque de su tarea. Una abstracción es muy superior: const count = countItems(array, 2);y los detalles de implementación pueden discutirse en su interior.
joeytwiddle
2
Esta no es la respuesta correcta porque la pregunta claramente pide no usar bucles. Comprueba mi solución que no usa bucles. stackoverflow.com/a/44743436/8211014
Luis Orantes
289

[ esta respuesta está un poco anticuada: lee las ediciones ]

Saluda a tus amigos: mapy filtery reducey forEachy everyetc.

(Solo ocasionalmente escribo for-loops en javascript, porque falta el alcance a nivel de bloque, por lo que debe usar una función como el cuerpo del bucle de todos modos si necesita capturar o clonar su índice o valor de iteración. For-loops son más eficientes en general, pero a veces necesita un cierre).

La forma más legible:

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

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

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

[....].reduce((total,x) => (x==2 ? total+1 : total), 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 puede ser elegante y convertirlo en una función prototipo:

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

Me gusta esto:

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

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


Edición 2017 :

Vaya, esta respuesta se ha vuelto más popular que la respuesta correcta. En realidad, solo usa la respuesta aceptada. Si bien esta respuesta puede ser linda, los compiladores js probablemente no optimizan (o no pueden debido a las especificaciones) tales casos. Entonces realmente 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;
        }
    }
});

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 lo llama .countEqo .countNonstricthace hincapié en que usa el ==operador).

También considere usar su propia estructura de datos de múltiples conjuntos (por ejemplo, como la de Python ' collections.Counter') para evitar tener que contar en primer lugar.

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 todavía quisiera la forma de programación funcional (o una línea de un solo uso sin anular el prototipo Array), podría escribirlo más brevemente hoy en día 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 ciclo for (tiempo O (N)), puede requerir memoria adicional O (N) (en lugar de memoria O (1)) porque casi ciertamente genera una matriz intermedia y luego cuenta los elementos de esa matriz intermedia.

ninjagecko
fuente
1
Esta es una buena solución FP, el único "problema" (irrelevante para la mayoría de los casos) crea una matriz intermedia.
tokland
1
@tokland: Si eso es una preocupación, puedes hacerloarray.reduce(function(total,x){return x==value? : total+1 : total}, 0)
ninjagecko
1
@ninjagecko ¿No debería haber solo un colon en el operador ternario? [...].reduce(function(total,x){return x==2 ? total+1 : total}, 0)
A.Krueger
2
@tokland Quizás el filtro no creará una matriz intermedia. Un buen compilador de optimización puede reconocer fácilmente que solo se usa la longitud de la matriz. Quizás ninguno de los compiladores JS actuales sea lo suficientemente inteligente como para hacer esto, pero eso no es importante. Como dice Fowler, "Si algo duele, entonces haz más". Es miope evitar las deficiencias del compilador escribiendo código pobre. Si el compilador apesta, corríjalo. mlafeldt.github.io/blog/if-it-hurts-do-it-more-often
John Henckel
Me parece que es una solución óptima 2017: const count = (list) => list.filter((x) => x == 2).length. Luego úselo llamando a count(list)donde la lista es una matriz de números. También puede hacer const count = (list) => list.filter((x) => x.someProp === 'crazyValue').lengthpara contar instancias de crazyValue en la matriz de objetos. Tenga en cuenta que es una coincidencia exacta para la propiedad.
agm1984
71

Actualización de ES6 a JS:

Tenga en cuenta que siempre debe usar triples iguales: ===para obtener una comparación correcta:

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

// Functional filter with an Arrow function
array.filter(x => x === 2).length  // -> 3

La siguiente función de flecha unánime (función lambda) en JS:

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

puede simplificarse a esta forma concisa para una sola entrada:

x => 2 * x

donde returnestá implícito

Sverrisson
fuente
¿La función de filtro es más eficaz que usar el es6 para el bucle?
Niklas
1
@Niklas, creo que es lo mismo (ya que ambos tienen que verificar todos los elementos, O (N)), pero supongo que depende del navegador y también del número de elementos y de la computadora en cuanto a lo que cabe en la memoria caché. Entonces, supongo que la respuesta es: "Es complejo" :)
Sverrisson
67

2017: si alguien todavía está interesado en la pregunta, mi solución es la siguiente:

const arrayToCount = [1, 2, 3, 5, 2, 8, 9, 2];
const result = arrayToCount.filter(i => i === 2).length;
console.log('number of the found elements: ' + result);

Raild
fuente
8

Si está usando lodash o subrayado, el método _.countBy proporcionará un objeto de totales agregados ingresados ​​por cada valor en la matriz. Puede convertir esto en una línea si solo necesita contar un valor:

_.countBy(['foo', 'foo', 'bar'])['foo']; // 2

Esto también funciona bien en matrices de números. La frase para su ejemplo sería:

_.countBy([1, 2, 3, 5, 2, 8, 9, 2])[2]; // 3
Coleman
fuente
55
Enorme exageración. Como crea contadores para todos los elementos únicos. Almacenamiento y tiempo desperdiciados.
metalim
5

La forma más extraña en que puedo pensar en hacer esto es:

(a.length-(' '+a.join(' ')+' ').split(' '+n+' ').join(' ').match(/ /g).length)+1

Dónde:

  • a es la matriz
  • n es el número a contar en la matriz

Mi sugerencia, use un tiempo o for loop ;-)

Gary Green
fuente
3

No usar un bucle generalmente significa entregar el proceso a algún método que use un bucle.

Aquí hay una forma en que nuestro codificador de odio de bucle puede satisfacer su odio, a un precio:

var a=[1, 2, 3, 5, 2, 8, 9, 2];

alert(String(a).replace(/[^2]+/g,'').length);


/*  returned value: (Number)
3
*/

También puede llamar repetidamente indexOf, si está disponible como método de matriz, y mover el puntero de búsqueda cada vez.

Esto no crea una nueva matriz y el bucle es más rápido que forEach o filter.

Podría hacer una diferencia si tienes un millón de miembros a los que mirar.

function countItems(arr, what){
    var count= 0, i;
    while((i= arr.indexOf(what, i))!= -1){
        ++count;
        ++i;
    }
    return count
}

countItems(a,2)

/*  returned value: (Number)
3
*/
Kennebec
fuente
2
Podría reducir su expresión regular a solo String(a).match(/2/g).length + 1, aunque tenga cuidado con esto o su implementación no funcionará bien con dos dígitos.
Gary Green
2
¿qué tal [2, 22, 2]?
Oduvan
2

La mayoría de las soluciones publicadas que utilizan funciones de matriz como el filtro están incompletas porque no están parametrizadas.

Aquí va una solución con la que el elemento a contar se puede establecer en tiempo de ejecución.

function elementsCount(elementToFind, total, number){
    return total += number==elementToFind;
}

var ar = [1, 2, 3, 5, 2, 8, 9, 2];
var elementToFind=2;
var result = ar.reduce(elementsCount.bind(this, elementToFind), 0);

La ventaja de este enfoque es que podría cambiar fácilmente la función para contar, por ejemplo, el número de elementos mayores que X.

También puede declarar la función reducir en línea

var ar = [1, 2, 3, 5, 2, 8, 9, 2];
var elementToFind=2;
var result = ar.reduce(function (elementToFind, total, number){
    return total += number==elementToFind;
}.bind(this, elementToFind), 0);
Luis Orantes
fuente
var elementToFind=2; ... function (elementToFind, total, number){ return total += number==elementToFind; }.bind(this, elementToFind) ...es más difícil de leer y no da ventaja sobre solo ... (acc, x) => acc += number == 2.... Sin embargo, me gusta su uso de en +=lugar de acc + (number == 2). Sin embargo, se siente como una sintaxis injustificada HACK.
masterxilo
2

Realmente, ¿por qué necesitarías mapo filterpara esto? reducenació para este tipo de operaciones:

[1, 2, 3, 5, 2, 8, 9, 2].reduce( (count,2)=>count+(item==val), 0);

¡Eso es! (si item==valen cada iteración, se agregará 1 al acumulador count, como truese resolverá 1).

Como una función:

function countInArray(arr, val) {
   return arr.reduce((count,item)=>count+(item==val),0)
}

O bien, adelante y extienda sus matrices:

Array.prototype.count = function(val) {
   return this.reduce((count,item)=>count+(item==val),0)
}
Yuval A.
fuente
2

Es mejor envolverlo en función:

let countNumber = (array,specificNumber) => {
    return array.filter(n => n == specificNumber).length
}

countNumber([1,2,3,4,5],3) // returns 1
Justin Herrera
fuente
2

Aquí hay una forma ES2017 + de obtener los recuentos de todos los elementos de la matriz en O (N):

const arr = [1, 2, 3, 5, 2, 8, 9, 2];
const counts = {};

arr.forEach((el) => {
  counts[el] = counts[el] ? (counts[el] += 1) : 1;
});

Opcionalmente, también puede ordenar la salida:

const countsSorted = Object.entries(counts).sort(([_, a], [__, b]) => a - b);

console.log (countSorted) para su matriz de ejemplo:

[
  [ '2', 3 ],
  [ '1', 1 ],
  [ '3', 1 ],
  [ '5', 1 ],
  [ '8', 1 ],
  [ '9', 1 ]
]
Yanick J. Steinbeck
fuente
1

Creo que lo que buscas es un enfoque funcional

    const arr = ['a', 'a', 'b', 'g', 'a', 'e'];
    const count = arr.filter(elem => elem === 'a').length;
    console.log(count); // Prints 3

elem === 'a' es la condición, reemplácela por la tuya.

Shantanu Bhadoria
fuente
No imprimirá 3, pero 0. Para solucionarlo, su segunda línea debe ser count = arr.filter(elem => elem === 'a').lengthocount = arr.filter(elem => {return elem === 'a'}).length
WPomier
1

Soy un fanático de la función de reducción de js array.

const myArray =[1, 2, 3, 5, 2, 8, 9, 2];
const count = myArray.reduce((count, num) => num === 2 ? count + 1 : count, 0)

De hecho, si realmente quieres ponerte elegante, puedes crear una función de conteo en el prototipo de matriz. Entonces puedes reutilizarlo.

Array.prototype.count = function(filterMethod) {
  return this.reduce((count, item) => filterMethod(item)? count + 1 : count, 0);
} 

Entonces hazlo

const myArray =[1, 2, 3, 5, 2, 8, 9, 2]
const count = myArray.count(x => x==2)
Scott Blanch
fuente
0

Solución por recursión

function count(arr, value) {
   if (arr.length === 1)    {
      return arr[0] === value ? 1 : 0;
   } else {
      return (arr.shift() === value ? 1 : 0) + count(arr, value);
   }
}

count([1,2,2,3,4,5,2], 2); // 3
Giffo
fuente
1
¿Esto maneja una matriz vacía?
Andrew Grimm
@AndrewGrimm tiene razón. El caso base es arr.length == 0
Justin Meiners
Buena solución! Estaba tratando de hacer algo usando la recursividad solo para practicar y tu ejemplo fue más elegante que lo que estaba haciendo. Definitivamente es una forma más compleja que usar filter, reduceo una simple forLoop, y también, más costosa cuando se mira el rendimiento, pero sigue siendo una excelente manera de hacerlo con recurrencia. Mi único cambio es: creo que sería mejor crear una función y agregar un filtro dentro para copiar la matriz y evitar una mutación de la matriz original, luego usar el recursivo como una función interna.
R. Marques
0

var arrayCount = [1,2,3,2,5,6,2,8];
var co = 0;
function findElement(){
    arrayCount.find(function(value, index) {
      if(value == 2)
        co++;
    });
    console.log( 'found' + ' ' + co + ' element with value 2');
}

Haría algo así:

var arrayCount = [1,2,3,4,5,6,7,8];

function countarr(){
  var dd = 0;
  arrayCount.forEach( function(s){
    dd++;
  });

  console.log(dd);
}

roni
fuente
0

Cree un nuevo método para la clase Array en el archivo de nivel central y úselo en todo su proyecto.

// say in app.js
Array.prototype.occurrence = function(val) {
  return this.filter(e => e === val).length;
}

Use esto en cualquier parte de su proyecto:

[1, 2, 4, 5, 2, 7, 2, 9].occurrence(2);
// above line returns 3
Rushikesh Bharad
fuente
0

Aquí hay una línea en javascript.

  1. Usa el mapa. Encuentre los valores coincidentes (v === 2)en la matriz, devolviendo una matriz de unos y ceros.
  2. Use Reducir. Agregue todos los valores de la matriz para el número total encontrado.
[1, 2, 3, 5, 2, 8, 9, 2]
  .map(function(v) {
    return v === 2 ? 1 : 0;
  })
  .reduce((a, b) => a + b, 0);

El resultado es 3.

Jake
fuente
0

Dependiendo de cómo quieras ejecutarlo:

const reduced = (array, val) => { // self explanatory
    return array.filter((element) => element === val).length;
}

console.log(reduced([1, 2, 3, 5, 2, 8, 9, 2], 2));

// 3

const reducer = (array) => { // array to set > set.forEach > map.set
    const count = new Map();
    const values = new Set(array);
    values.forEach((element)=> {
        count.set(element, array.filter((arrayElement) => arrayElement === element).length);
    });
    return count;
}
console.log(reducer([1, 2, 3, 5, 2, 8, 9, 2]));

// Map(6) {1 => 1, 2 => 3, 3 => 1, 5 => 1, 8 => 1, …}
Mohammad Hassan
fuente
-7

Puede usar la propiedad de longitud en la matriz de JavaScript:

var myarray = [];
var count = myarray.length;//return 0

myarray = [1,2];
count = myarray.length;//return 2
Bhavik Bhavsar
fuente
Si lo filtra primero, puede usar la longitud. es decir, array.filter (x => x === 2) .length
Scott Blanch