¿Cómo encontrar los índices de todas las ocurrencias de un elemento en una matriz?

6 minutos de lectura

avatar de usuario de norbdum
norbdum

Estoy tratando de encontrar los índices de todas las instancias de un elemento, por ejemplo, “Nano”, en una matriz de JavaScript.

var Cars = ["Nano", "Volvo", "BMW", "Nano", "VW", "Nano"];

Lo intenté jQuery.inArrayo de manera similar, .índice de()pero solo dio el índice de la última instancia del elemento, es decir, 5 en este caso.

¿Cómo lo obtengo para todas las instancias?

avatar de usuario de nnnnnn
nnnnnn

Él .indexOf() método tiene un segundo parámetro opcional que especifica el índice desde el que comenzar a buscar, por lo que puede llamarlo en un bucle para encontrar todas las instancias de un valor particular:

function getAllIndexes(arr, val) {
    var indexes = [], i = -1;
    while ((i = arr.indexOf(val, i+1)) != -1){
        indexes.push(i);
    }
    return indexes;
}

var indexes = getAllIndexes(Cars, "Nano");

Realmente no deja en claro cómo desea usar los índices, por lo que mi función los devuelve como una matriz (o devuelve una matriz vacía si no se encuentra el valor), pero podría hacer otra cosa con los valores de índice individuales dentro del bucle.

ACTUALIZACIÓN: según el comentario de VisioN, un bucle for simple haría el mismo trabajo de manera más eficiente, y es más fácil de entender y, por lo tanto, más fácil de mantener:

function getAllIndexes(arr, val) {
    var indexes = [], i;
    for(i = 0; i < arr.length; i++)
        if (arr[i] === val)
            indexes.push(i);
    return indexes;
}

  • No parece ser la alternativa más rápida a un solo for bucle con relleno de matriz de índice.

    – Visión

    27 de diciembre de 2013 a las 10:04

  • @VisioN: sí, un ciclo simple para iterar sobre la matriz también sería más simple, pero dado que el OP mencionó intentar usar .indexOf() Quería demostrar que puede hacer el trabajo. (Supongo que pensé que el OP podría descubrir cómo hacerlo con un ciclo for). Por supuesto, hay otras formas de hacerlo, por ejemplo, Cars.reduce(function(a, v, i) { if (v==="Nano") a.push(i); return a; }, []);

    – nnnnnn

    27 de diciembre de 2013 a las 10:07


  • Puedo decir que eres de América del Norte porque solías indexes en vez de indices :PAG

    – 4castillo

    14/04/2016 a las 21:22


  • @4castle – Ja. No no soy. “Índices” e “índices” son correctos, y tiendo a alternar entre los dos. Nunca había pensado en eso como una cosa del dialecto regional. Interesante.

    – nnnnnn

    14/04/2016 a las 22:31


  • @IgorFomenko – Gracias por la sugerencia. No es realmente un “cálculo”, es solo una búsqueda de propiedades, pero en realidad hago bucles de código a menudo según su sugerencia, pero generalmente no lo hago en las respuestas de StackOverflow si no es directamente relevante para la pregunta. ¿Qué tan seguro está de que el compilador JS no hará esa optimización automáticamente detrás de escena?

    – nnnnnn

    20 de agosto de 2020 a las 9:43


Otra solución alternativa es utilizar Array.prototype.reduce():

["Nano","Volvo","BMW","Nano","VW","Nano"].reduce(function(a, e, i) {
    if (e === 'Nano')
        a.push(i);
    return a;
}, []);   // [0, 3, 5]

NÓTESE BIEN: Compruebe el compatibilidad del navegador por reduce método y uso polirelleno si es requerido.

  • +1. Coincidencia divertida: acabo de editar mi respuesta a su comentario debajo de mi respuesta para sugerir exactamente esta solución, luego actualizo y veo que ya había codificado lo mismo con solo un nombre de variable diferente.

    – nnnnnn

    27 de diciembre de 2013 a las 10:15

  • @nnnnnn :) Sí, pensé que tal vez reduce podría ser una buena alternativa.

    – Visión

    27 de diciembre de 2013 a las 10:16

  • array.reduce((a, e, i) => (e === value) ? a.concat(i) : a, [])

    – yckart

    21 de diciembre de 2016 a las 20:46

  • busqué en Google contat es más lento que pushpor lo que me quedo con la respuesta.

    – André Elrico

    19 de septiembre de 2019 a las 8:16

  • Sí, por favor no use concat aquí: está asignando un objeto de matriz completamente nuevo en cada devolución de llamada y arrojando el objeto anterior al recolector de basura sin ningún beneficio proporcional en la legibilidad.

    – ggorlen

    2 oct 2020 a las 17:17


avatar de usuario de yckart
yckart

Otro enfoque usando matriz.prototipo.mapa() y matriz.prototipo.filtro():

var indices = array.map((e, i) => e === value ? i : '').filter(String)

  • Genial, funciona. ¿Puedes explicar cuál es el papel del filtro (String)?

    – Muthamizhchelvan. V

    03/01/2018 a las 19:40

  • @Muthu map(…) comprueba en cada iteración la igualdad de e y value. Cuando coinciden, se devuelve el índice; de ​​lo contrario, una cadena vacía. Para deshacerse de esos valores falsos, filter(String) se asegura de que el resultado solo contenga valores que sean del tipo de cadena y NO estén vacíos. filter(String) también podría escribirse como: filter(e => e !== '')

    – yckart

    6 de enero de 2018 a las 19:13


  • …o: String(thing) coacciona cualquier cosa a una cadena. Array#filter devuelve una matriz de todos los valores para los que la condición es veraz. Porque las cadenas vacías son falsoesos NO están incluidos en la matriz.

    – yckart

    6 de enero de 2018 a las 19:13


  • Gracias por tu explicación, me es de gran ayuda.

    – Muthamizhchelvan. V

    7 de enero de 2018 a las 17:32

  • Me confundiría si viera esto en un proyecto. Se lee como “Filtrar a cadenas”, lo que significa mantener solo si es una cadena. Y luego, la matriz resultante serían índices como cadenas, no números.

    –Michael Pearson

    14 de julio de 2019 a las 4:08

avatar de usuario de awmidas
awmidas

Manera más simple con estilo es6.

const indexOfAll = (arr, val) => arr.reduce((acc, el, i) => (el === val ? [...acc, i] : acc), []);


//Examples:
var cars = ["Nano", "Volvo", "BMW", "Nano", "VW", "Nano"];
indexOfAll(cars, "Nano"); //[0, 3, 5]
indexOfAll([1, 2, 3, 1, 2, 3], 1); // [0,3]
indexOfAll([1, 2, 3], 4); // []

Avatar de usuario de Zac Delventhal
Zac Delventhal

Puede escribir una solución legible simple para esto usando ambos map y filter:

const nanoIndexes = Cars
  .map((car, i) => car === 'Nano' ? i : -1)
  .filter(index => index !== -1);

EDITAR: si no necesita admitir IE/Edge (o está transpilando su código), ES2019 nos dio mapa planoque le permite hacer esto en una sola línea:

const nanoIndexes = Cars.flatMap((car, i) => car === 'Nano' ? i : []);

  • Me gustó esta respuesta. Pero qué tal: cambiar a esto: .map((car, i) => car === 'Nano' ? i : null).filter(i => i);

    – Anton vBR

    16 abr a las 19:09

  • Cualquiera de los dos funciona. El problema con el uso de un nulo que no sea un número es que hará que escribirlo en TypeScript sea mucho más molesto. Si solo está usando JavaScript, null funciona bien.

    – Zac Delventhal

    17 abr a las 1:31

Avatar de usuario de JKhan
JKhan

Solo quiero actualizar con otro método fácil.

También puede usar el método forEach.

var Cars = ["Nano", "Volvo", "BMW", "Nano", "VW", "Nano"];

var result = [];

Cars.forEach((car, index) => car === 'Nano' ? result.push(index) : null)

  • Me gustó esta respuesta. Pero qué tal: cambiar a esto: .map((car, i) => car === 'Nano' ? i : null).filter(i => i);

    – Anton vBR

    16 abr a las 19:09

  • Cualquiera de los dos funciona. El problema con el uso de un nulo que no sea un número es que hará que escribirlo en TypeScript sea mucho más molesto. Si solo está usando JavaScript, null funciona bien.

    – Zac Delventhal

    17 abr a las 1:31

avatar de usuario de abalter
abatir

Nota: MDN da un método usando un ciclo while:

var indices = [];
var array = ['a', 'b', 'a', 'c', 'a', 'd'];
var element="a";
var idx = array.indexOf(element);
while (idx != -1) {
  indices.push(idx);
  idx = array.indexOf(element, idx + 1);
}

No diría que es mejor que otras respuestas. Solo interesante.

¿Ha sido útil esta solución?