Combinando matrices de JavaScript -
PorKyle Simpson es
Esta es una publicación rápida y sencilla sobre técnicas de JavaScript. Vamos a cubrir diferentes métodos para combinar/fusionar dos matrices JS y los pros y los contras de cada enfoque.
Comenzamos con el escenario:
var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];var b = [ "foo", "bar", "baz", "bam", "bun", "divertido" ];
La concatenación simple de a
y b
sería, obviamente,:
[1, 2, 3, 4, 5, 6, 7, 8, 9, "foo", "bar", "baz", "bam" "bun", "divertido"]
concat(..)
El enfoque más común es:
var c = a.concat( b );a; // [1,2,3,4,5,6,7,8,9]b; // ["foo","bar","baz","bam","bun","diversión"]c; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","divertido"]
Como puedes ver, c
es completamente nuevo array
y representa la combinación de los dos a
y b
las matrices, dejándolo a
intacto b
. Sencillo, ¿verdad?
¿Qué pasa si a
son 10.000 artículos y b
son 10.000 artículos? c
Ahora hay 20.000 elementos, lo que básicamente equivale a duplicar el uso de memoria de a
y b
.
“¡No hay problema!”, dice. Simplemente los desarmamos a
y b
por eso se recolectan basura, ¿verdad? ¡Problema resuelto!
a = b = nulo; // `a` y `b` pueden desaparecer ahora
Bueno. Por sólo un par de array
s pequeños, esto está bien. Pero para array
archivos grandes, o para repetir este proceso regularmente muchas veces, o para trabajar en entornos con memoria limitada, deja mucho que desear.
Inserción en bucle
Bien, agregamos array
el contenido de uno al otro, usando Array#push(..)
:
// `b` sobre `a`for (var i=0; i b.length; i++) { a.push( b[i] );}a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]b = null;
Ahora, a
tiene el resultado tanto del original a
como del contenido de b
.
Parecería que es mejor para la memoria.
Pero, ¿y si a
fuera pequeño y b
comparativamente muy grande? Por razones de memoria y velocidad, probablemente desee colocar el más pequeño a
en el frente b
en lugar del más largo b
en el final a
. No hay problema, simplemente reemplaza push(..)
con unshift(..)
y haz un bucle en la dirección opuesta:
// `a` en `b`:for (var i=a.length-1; i = 0; i--) { b.unshift( a[i] );}b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]a = null;
Trucos funcionales
Unfortunately, for
loops are ugly and harder to maintain. Can we do any better?
Here’s our first attempt, using Array#reduce
:
// `b` onto `a`:a = b.reduce( function(coll,item){ coll.push( item ); return coll;}, a );a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]// or `a` into `b`:b = a.reduceRight( function(coll,item){ coll.unshift( item ); return coll;}, b );b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
Array#reduce(..)
and Array#reduceRight(..)
are nice, but they are a tad clunky. ES6 =
arrow-functions will slim them down slightly, but it’s still requiring a function-per-item call, which is unfortunate.
What about:
// `b` onto `a`:a.push.apply( a, b );a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]// or `a` into `b`:b.unshift.apply( b, a );b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]
That’s a lot nicer, right!? Especially since the unshift(..)
approach here doesn’t need to worry about the reverse ordering as in the previous attempts. ES6’s spread operator will be even nicer: a.push( ...b )
or b.unshift( ...a )
.
But, things aren’t as rosy as they might seem. In both cases, passing either a
or b
to apply(..)
‘s second argument (or via the ...
spread operator) means that the array is being spread out as arguments to the function.
The first major problem is that we’re effectively doubling the size (temporarily, of course!) of the thing being appended by essentially copying its contents to the stack for the function call. Moreover, different JS engines have different implementation-dependent limitations to the number of arguments that can be passed.
So, if the array
being added on has a million items in it, you’d almost certainly way exceed the size of the size of the stack allowed for that push(..)
or unshift(..)
call. Ugh. It’ll work just fine for a few thousand elements, but you have to be careful not to exceed a reasonably safe limit.
Note: You can try the same thing with splice(..)
, but you’ll have the same conclusions as with push(..)
/ unshift(..)
.
One option would be to use this approach, but batch up segments at the max safe size:
function combineInto(a,b) { var len = a.length; for (var i=0; i len; i=i+5000) { b.unshift.apply( b, a.slice( i, i+5000 ) ); }}
Wait, we’re going backwards in terms of readability (and perhaps even performance!). Let’s quit before we give up all our gains so far.
Summary
Array#concat(..)
es el enfoque probado y verdadero para combinar dos (¡o más!) matrices. Pero el peligro oculto es que está creando una nueva matriz en lugar de modificar una de las existentes.
Hay opciones que se modifican in situ, pero tienen varias compensaciones.
Teniendo en cuenta las diversas ventajas y desventajas, quizás la mejor de todas las opciones (incluidas otras que no se muestran) sea reduce(..)
y reduceRight(..)
.
Cualquiera que sea su elección, probablemente sea una buena idea pensar críticamente sobre su estrategia de fusión de matrices en lugar de darla por sentado.
Acerca de Kyle Simpson
Kyle Simpson es un ingeniero de software orientado a la web, ampliamente aclamado por su serie de libros “You Don't Know JS” y por casi 1 millón de horas de vistas de sus cursos en línea. El superpoder de Kyle es hacer mejores preguntas, quien cree profundamente en utilizar al máximo las herramientas mínimamente necesarias para cualquier tarea. Como “tecnólogo centrado en lo humano”, le apasiona unir a los humanos y la tecnología, haciendo evolucionar las organizaciones de ingeniería para resolver los problemas correctos, de maneras más simples. Kyle siempre luchará por las personas detrás de los píxeles.
Publicaciones de github.com
Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.
Te podría interesar...