Este es un seguimiento de mi pregunta anterior Arreglos de JavaScript: ¿cómo compacto el exceso de longitud?
¡Acabo de descubrir que todos los motores de JavaScript que tengo (Chromium, Node.js, Firefox) son extremadamente ineficientes con arreglos dispersos! Ejemplo:
[,1,2,3,,,,,9,,,].find((x,i,a) => console.log(x,i))
Resulta que todos los agujeros también son alcanzados por esta búsqueda. Pero en un caso de uso del mundo real de arreglos dispersos grandes, ¡esto es extremadamente ineficiente!
Se podría argumentar que formalmente se debería exigir que find devuelva indefinidos los valores faltantes en lugar de omitirlos y, por lo tanto, nunca poder devolverlos. Pero esa parece ser una propiedad raramente útil de estas funciones de iteración y, en cambio, la especificación debería decir que se deben omitir los agujeros en las matrices dispersas.
Parece que se reduce a la iteración con el operador in
vs. of
.
Entonces, ¿podría haber una forma de redefinir o modificar la iteración de estas funciones de iteración para que se basen solo en los índices que realmente existen? Especialmente me gustaría encontrar el primer índice y el último índice en la matriz, y también el siguiente índice dado un índice (que puede o no ser el índice de un agujero).
La propiedad de longitud en un Array toma el índice del último elemento y agrega uno. Entonces, si tiene una matriz con agujeros entre el índice 0 y el 100, y un elemento en el índice 101, la longitud devolverá 101, ya que es el último índice + 1.
Lo anterior sucede independientemente de cuántos elementos haya en el Array.
Esta es una característica documentada. Si desea ignorar los espacios vacíos, simplemente verifique si el elemento en cualquier índice es falso y luego no siga la lógica. Eres más que bienvenido a modificar prototipos para hacer esto, por tu cuenta y riesgo, por supuesto.
La respuesta es: sí , find, findIndex, findLast, findLastIndex de hecho no son eficientes en grandes arreglos dispersos, pero no , eso no es cierto para todas las funciones iterativas de arreglos.
De hecho, forEach, filter, map y reduce, incluso some and every, no llaman a la función de devolución de llamada en los huecos de las matrices dispersas, que probé en Chromium, Firefox y Node.JS. Por lo tanto, si la intención de usar findLastIndex es reemplazar el uso ingenuo de la propiedad de longitud de matriz que no es confiable (y, por lo tanto, generalmente inútil si a menudo es útil en la práctica cuando se ignoran muchos escenarios de matriz dispersa), entonces se puede hacer lo siguiente:
(function(arr) { try { return arr.reduceRight( function(r,x,i,a) { console.log(r,x,i,a); throw i; }, -1); } catch(r) { if(typeof r == 'number') return r; else throw r; } })([,,,3,,,6,,,9,,,])
No sé si hay una forma más suave de salir de un bucle de función de iterador que no sea arrojar el valor del resultado, pero funciona y acorta lo que de otro modo continuaría después de que ya se haya encontrado el resultado.
En el caso de find(Index) desde el principio (en lugar del final), donde una matriz dispersa también podría causar millones de iteraciones incluso antes de que se encuentre el primer elemento, se puede lograr lo mismo con forEach:
let testArray = []; testArray[20000000] = "first"; testArray[40000000] = "last"; (function(arr) { try { arr.forEach( function(x,i,a) { console.log(x,i,a); throw i; }); return -1; } catch(r) { if(typeof r == "number") return r; else throw r; } })(testArray)
A medida que ejecuta esto con diferentes valores de índice (creando una escasez inicial de tamaño diferente), nota que (nuevamente en los tres, Chromium, Firefox y Node.JS) la implementación es ineficiente ya que parece iterar internamente en lugar de mantener un puntero directo a el primer índice real, el último índice real y, potencialmente, incluso los próximos punteros para omitir agujeros grandes. Pero esto está bien, esas son optimizaciones que se pueden agregar, al menos la especificación no requiere que la devolución de llamada se invoque en los agujeros de una matriz dispersa como parece ser el caso con find (que creo que es un mala idea).
Lo siguiente entra en mi propia caja de herramientas general a partir de ahora:
Array.prototype.findActualLastIndex = function(f) { try { return this.reduceRight( function(r, x, i, a) { if(f(x, i, a)) throw i; else return r; }, -1); } catch(r) { if(typeof r == 'number') return r; else throw r; } } Array.prototype.findActualIndex = function(f) { try { return this.reduce( function(r, x, i, a) { if(f(x, i, a)) throw i; else return r; }, -1); } catch(r) { if(typeof r == 'number') return r; else throw r; } }
Espero que esto pueda ayudar a otros. No está funcionando de manera óptima con enormes regiones libres en los extremos, pero es lo mejor que podemos sacar de la versión actual de los motores de JavaScript.