Cómo extender una matriz de JavaScript existente con otra matriz, sin crear una nueva matriz

974

No parece haber una manera de extender una matriz JavaScript existente con otra matriz, es decir, emular el extendmétodo de Python .

Quiero lograr lo siguiente:

>>> a = [1, 2]
[1, 2]
>>> b = [3, 4, 5]
[3, 4, 5]
>>> SOMETHING HERE
>>> a
[1, 2, 3, 4, 5]

Sé que hay un a.concat(b)método, pero crea una nueva matriz en lugar de simplemente extender la primera. Me gustaría un algoritmo que funcione de manera eficiente cuando aes significativamente mayor que b(es decir, uno que no se copia a).

Nota: ¿ Esto no es un duplicado de Cómo agregar algo a una matriz? - el objetivo aquí es agregar todo el contenido de una matriz a la otra, y hacerlo "en su lugar", es decir, sin copiar todos los elementos de la matriz extendida.

DzinX
fuente
55
Desde el comentario de @ Cepillo de dientes en una respuesta: a.push(...b). Es similar en concepto a la respuesta principal, pero actualizado para ES6.
tscizzle

Respuestas:

1501

El .pushmétodo puede tomar múltiples argumentos. Puede usar el operador de propagación para pasar todos los elementos de la segunda matriz como argumentos para .push:

>>> a.push(...b)

Si su navegador no es compatible con ECMAScript 6, puede usar .applyen su lugar:

>>> a.push.apply(a, b)

O tal vez, si crees que está más claro:

>>> Array.prototype.push.apply(a,b)

Tenga en cuenta que todas estas soluciones fallarán con un error de desbordamiento de la pila si la matriz bes demasiado larga (el problema comienza en aproximadamente 100,000 elementos, dependiendo del navegador).
Si no puede garantizar que bsea ​​lo suficientemente breve, debe utilizar una técnica estándar basada en bucles descrita en la otra respuesta.

DzinX
fuente
3
Creo que esta es tu mejor apuesta. Cualquier otra cosa va a implicar iteración u otro esfuerzo de aplicar ()
Peter Bailey
44
Esta respuesta fallará si "b" (la matriz para extender) es grande (> 150000 entradas aproximadamente en Chrome según mis pruebas). Debería usar un bucle for, o incluso mejor usar la función incorporada "forEach" en "b". Vea mi respuesta: stackoverflow.com/questions/1374126/…
jcdude
35
@Deqing: el pushmétodo de matriz puede tomar cualquier número de argumentos, que luego se envían a la parte posterior de la matriz. Entonces, a.push('x', 'y', 'z')es una llamada válida que se extenderá aen 3 elementos. applyes un método de cualquier función que toma una matriz y usa sus elementos como si todos se hubieran dado explícitamente como elementos posicionales a la función. Por a.push.apply(a, ['x', 'y', 'z'])lo tanto , también ampliaría la matriz en 3 elementos. Además, applytoma un contexto como primer argumento (lo pasamos anuevamente allí para adjuntarlo a).
DzinX
Nota: en este escenario, el primer valor de retorno no es [1,2,3,4,5] sino 5 mientras que a == [1,2,3,4,5] después.
jawo
1
Sin embargo, otro un poco menos confuso (y no mucho más) invocación sería: [].push.apply(a, b).
niry
260

Actualización 2018 : Una mejor respuesta es una más reciente mío : a.push(...b). No vuelvas a votar este, ya que en realidad nunca respondió la pregunta, pero fue un truco de 2015 sobre el primer hit en Google :)


Para aquellos que simplemente buscaron "JavaScript array extender" y llegaron aquí, pueden usarlo muy bien Array.concat.

var a = [1, 2, 3];
a = a.concat([5, 4, 3]);

Concat devolverá una copia de la nueva matriz, ya que el iniciador de subprocesos no quería. Pero puede que no te importe (ciertamente para la mayoría de los usos, esto estará bien).


También hay un buen azúcar ECMAScript 6 para esto en forma del operador de propagación:

const a = [1, 2, 3];
const b = [...a, 5, 4, 3];

(También copia).

odinho - Velmont
fuente
43
La pregunta decía claramente: "¿sin crear una nueva matriz?"
marchita el
10
@Wilt: Esto es cierto, la respuesta dice por qué :) Esto fue golpeado por primera vez en Google desde hace mucho tiempo. También las otras soluciones eran feas, querían algo ideomático y agradable. Aunque hoy en día arr.push(...arr2)es más nuevo y mejor y una respuesta técnicamente correcta (tm) a esta pregunta en particular.
odinho - Velmont
77
Te escuché, pero busqué "javascript append array sin crear una nueva matriz" , entonces es triste ver que una respuesta votada 60 veces más arriba no responde la pregunta real;) No voté abajo, ya que lo dejaste claro en tu respuesta.
marchita el
@Wilt y busqué "js extend array con array" y llegué aquí así que gracias :)
Davide hace
202

Debe usar una técnica basada en bucles. Otras respuestas en esta página que se basan en el uso .applypueden fallar para matrices grandes.

Una implementación bastante concisa basada en bucles es:

Array.prototype.extend = function (other_array) {
    /* You should include a test to check whether other_array really is an array */
    other_array.forEach(function(v) {this.push(v)}, this);
}

Luego puede hacer lo siguiente:

var a = [1,2,3];
var b = [5,4,3];
a.extend(b);

La respuesta de DzinX (usando push.apply) y otros .applymétodos basados ​​fallan cuando la matriz que estamos agregando es grande (las pruebas muestran que para mí grande es> 150,000 entradas aproximadamente en Chrome y> 500,000 entradas en Firefox). Puede ver este error en este jsperf .

Se produce un error porque se excede el tamaño de la pila de llamadas cuando se llama a 'Function.prototype.apply' con una matriz grande como segundo argumento. (MDN tiene una nota sobre los peligros de exceder el tamaño de la pila de llamadas usando Function.prototype.apply ; consulte la sección titulada "aplicar y funciones incorporadas").

Para una comparación de velocidad con otras respuestas en esta página, consulte este jsperf (gracias a EaterOfCode). La implementación basada en bucles es similar en velocidad al uso Array.push.apply, pero tiende a ser un poco más lenta que Array.slice.apply.

Curiosamente, si la matriz que está agregando es escasa, el forEachmétodo basado arriba puede aprovechar la escasez y superar a los .applymétodos basados; echa un vistazo a este jsperf si quieres probar esto por sí mismo.

Por cierto, no se sienta tentado (¡como lo estaba yo!) De acortar aún más la implementación de forEach para:

Array.prototype.extend = function (array) {
    array.forEach(this.push, this);
}

¡porque esto produce resultados basura! ¿Por qué? Porque Array.prototype.forEachproporciona tres argumentos para la función que llama: estos son: (element_value, element_index, source_array). ¡Todos estos serán empujados a su primera matriz para cada iteración de forEachsi usa "forEach (this.push, this)"!

jcdude
fuente
1
ps para probar si other_array es realmente una matriz, elija una de las opciones descritas aquí: stackoverflow.com/questions/767486/…
jcdude
66
Buena respuesta. No estaba al tanto de este problema. He citado su respuesta aquí: stackoverflow.com/a/4156156/96100
Tim Down
2
.push.applyen realidad es mucho más rápido que .forEachen v8, el más rápido sigue siendo un bucle en línea.
Benjamin Gruenbaum
1
@BenjaminGruenbaum: ¿podría publicar un enlace a algunos resultados que lo demuestren? Por lo que puedo ver en los resultados recopilados en el jsperf vinculado al final de este comentario, el uso .forEaches más rápido que .push.applyen Chrome / Chromium (en todas las versiones desde v25). No he podido probar v8 de forma aislada, pero si lo has hecho, vincula tus resultados. Ver jsperf: jsperf.com/array-extending-push-vs-concat/5
jcdude
1
@jcdude su jsperf no es válido. Como todos los elementos de su matriz lo están undefined, .forEachlos omitirá, haciéndolo más rápido. .splicees en realidad el más rápido? jsperf.com/array-extending-push-vs-concat/18
EaterOfCode
153

Siento que lo más elegante en estos días es:

arr1.push(...arr2);

El artículo de MDN sobre el operador de propagación menciona esta agradable forma azucarada en ES2015 (ES6):

Un mejor empujón

Ejemplo: push se usa a menudo para empujar una matriz al final de una matriz existente. En ES5 esto a menudo se hace como:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// Append all items from arr2 onto arr1
Array.prototype.push.apply(arr1, arr2);

En ES6 con propagación esto se convierte en:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

Tenga en cuenta que arr2no puede ser enorme (manténgalo en menos de 100 000 artículos), porque la pila de llamadas se desborda, según la respuesta de jcdude.

odinho - Velmont
fuente
1
Solo una advertencia de un error que acabo de cometer. La versión ES6 está muy cerca arr1.push(arr2). Esto puede ser un problema, por lo que arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)el resultado es una matriz de matrices en arr1 == [['a','b','d']]lugar de dos matrices combinadas. Es un error fácil de cometer. Prefiero su segunda respuesta a continuación por este motivo. stackoverflow.com/a/31521404/4808079
Seph Reed
Hay una conveniencia cuando se usa .concates que el segundo argumento no debe ser una matriz. Esto se puede lograr combinando el operador de propagación con concat, por ejemploarr1.push(...[].concat(arrayOrSingleItem))
Steven Pribilinskiy
Este es un Javascript moderno y elegante para los casos en que la matriz de destino no está vacía.
timbo
¿Es esto lo suficientemente rápido?
Roel
@RoelDelosReyes: Sí.
odinho - Velmont
46

Primero algunas palabras sobre apply()JavaScript para ayudar a entender por qué lo usamos:

El apply()método llama a una función con un thisvalor dado y los argumentos se proporcionan como una matriz.

Push espera una lista de elementos para agregar a la matriz. El apply()método, sin embargo, lleva a los argumentos esperados para la llamada de función como una matriz. Esto nos permite fácilmente pushlos elementos de una matriz en otra matriz con el push()método incorporado .

Imagina que tienes estas matrices:

var a = [1, 2, 3, 4];
var b = [5, 6, 7];

y simplemente haz esto:

Array.prototype.push.apply(a, b);

El resultado será:

a = [1, 2, 3, 4, 5, 6, 7];

Lo mismo se puede hacer en ES6 utilizando el operador de propagación (" ...") de esta manera:

a.push(...b); //a = [1, 2, 3, 4, 5, 6, 7]; 

Más corto y mejor, pero no totalmente compatible en todos los navegadores en este momento.

Además, si desea mover todo, desde la matriz ba a, vaciar ben el proceso, puede hacer esto:

while(b.length) {
  a.push(b.shift());
} 

y el resultado será el siguiente:

a = [1, 2, 3, 4, 5, 6, 7];
b = [];
Alireza
fuente
1
o while (b.length) { a.push(b.shift()); }bien?
LarsW
37

Si desea usar jQuery, hay $ .merge ()

Ejemplo:

a = [1, 2];
b = [3, 4, 5];
$.merge(a,b);

Resultado: a = [1, 2, 3, 4, 5]

Jules Colle
fuente
1
¿Cómo puedo encontrar la implementación (en el código fuente) de jQuery.merge()?
ma11hew28
Me tomó 10 minutos: jQuery es de código abierto y la fuente se publica en Github. Hice una búsqueda de "fusión" y más adelante encontré lo que parecía ser la definición de la función de fusión en el archivo core.js, consulte: github.com/jquery/jquery/blob/master/src/core.js#L331
amn
19

Me gusta el a.push.apply(a, b)método descrito anteriormente, y si lo desea, siempre puede crear una función de biblioteca como esta:

Array.prototype.append = function(array)
{
    this.push.apply(this, array)
}

y úsalo así

a = [1,2]
b = [3,4]

a.append(b)
Pizzaiola Gorgonzola
fuente
66
El método push.apply no debe usarse, ya que puede causar un desbordamiento de la pila (y, por lo tanto, fallar) si su argumento "array" es una matriz grande (por ejemplo,> ~ 150000 entradas en Chrome). Debería usar "array.forEach" - vea mi respuesta: stackoverflow.com/a/17368101/1280629
jcdude
12

Es posible hacerlo usando splice():

b.unshift(b.length)
b.unshift(a.length)
Array.prototype.splice.apply(a,b) 
b.shift() // Restore b
b.shift() // 

Pero a pesar de ser más feo, no es más rápido que push.apply, al menos no en Firefox 3.0.

Rafał Dowgird
fuente
99
He encontrado lo mismo, el empalme no proporciona mejoras de rendimiento para impulsar cada elemento hasta aproximadamente 10,000 matrices de elementos jsperf.com/splice-vs-push
Drew
1
+1 Gracias por agregar esto y la comparación de rendimiento, me ahorró el esfuerzo de probar este método.
jjrv
1
El uso de splice.apply o push.apply puede fallar debido a un desbordamiento de la pila si la matriz b es grande. También son más lentos que el uso de un bucle "for" o "forEach": consulte este jsPerf: jsperf.com/array-extending-push-vs-concat/5 y mi respuesta stackoverflow.com/questions/1374126/…
jcdude
4

Esta solución funciona para mí (usando el operador de propagación de ECMAScript 6):

let array = ['my', 'solution', 'works'];
let newArray = [];
let newArray2 = [];
newArray.push(...array); // Adding to same array
newArray2.push([...array]); // Adding as child/leaf/sub-array
console.log(newArray);
console.log(newArray2);

Pankaj Shrivastava
fuente
1

Puede crear un polyfill para extender como tengo a continuación. Se agregará a la matriz; en el lugar y regresar, para que pueda encadenar otros métodos.

if (Array.prototype.extend === undefined) {
  Array.prototype.extend = function(other) {
    this.push.apply(this, arguments.length > 1 ? arguments : other);
    return this;
  };
}

function print() {
  document.body.innerHTML += [].map.call(arguments, function(item) {
    return typeof item === 'object' ? JSON.stringify(item) : item;
  }).join(' ') + '\n';
}
document.body.innerHTML = '';

var a = [1, 2, 3];
var b = [4, 5, 6];

print('Concat');
print('(1)', a.concat(b));
print('(2)', a.concat(b));
print('(3)', a.concat(4, 5, 6));

print('\nExtend');
print('(1)', a.extend(b));
print('(2)', a.extend(b));
print('(3)', a.extend(4, 5, 6));
body {
  font-family: monospace;
  white-space: pre;
}

Mr. Polywhirl
fuente
0

Combinando las respuestas ...

Array.prototype.extend = function(array) {
    if (array.length < 150000) {
        this.push.apply(this, array)
    } else {
        for (var i = 0, len = array.length; i < len; ++i) {
            this.push(array[i]);
        };
    }  
}
zCoder
fuente
99
No, no hay necesidad de hacer esto. Un bucle for usando forEach es más rápido que usar push.apply, y funciona sin importar la longitud de la matriz para extender. Eche un vistazo a mi respuesta revisada: stackoverflow.com/a/17368101/1280629 En cualquier caso, ¿cómo sabe que 150000 es el número correcto para todos los navegadores? Esto es un dulce de azúcar.
jcdude
jajaja mi respuesta es irreconocible, pero parece ser una combinación de otras encontradas en ese momento, es decir, un resumen. no se preocupe
zCoder
0

Otra solución para fusionar más de dos matrices.

var a = [1, 2],
    b = [3, 4, 5],
    c = [6, 7];

// Merge the contents of multiple arrays together into the first array
var mergeArrays = function() {
 var i, len = arguments.length;
 if (len > 1) {
  for (i = 1; i < len; i++) {
    arguments[0].push.apply(arguments[0], arguments[i]);
  }
 }
};

Luego llame e imprima como:

mergeArrays(a, b, c);
console.log(a)

La salida será: Array [1, 2, 3, 4, 5, 6, 7]

usuario3126309
fuente
-1

La respuesta es super simple.

>>> a = [1, 2]
[1, 2]
>>> b = [3, 4, 5]
[3, 4, 5]
>>> SOMETHING HERE
(The following code will combine the two arrays.)

a = a.concat(b);

>>> a
[1, 2, 3, 4, 5]

Concat actúa de manera muy similar a la concatenación de cadenas de JavaScript. Devolverá una combinación del parámetro que pones en la función concat al final de la matriz en la que llamas a la función. El quid es que tienes que asignar el valor devuelto a una variable o se pierde. Así por ejemplo

a.concat(b);  <--- This does absolutely nothing since it is just returning the combined arrays, but it doesn't do anything with it.
Timothy Trousdale
fuente
2
Como se indica claramente en los documentos de Array.prototype.concat()MDN, esto devolverá una nueva matriz , no se agregará a la matriz existente como OP estaba pidiendo claramente.
marchita el
-2

Use en Array.extendlugar de Array.pushpara> 150,000 registros.

if (!Array.prototype.extend) {
  Array.prototype.extend = function(arr) {
    if (!Array.isArray(arr)) {
      return this;
    }

    for (let record of arr) {
      this.push(record);
    }

    return this;
  };
}
Mahdi Pedram
fuente
-6

Súper simple, no cuenta con operadores de propagación ni aplica, si eso es un problema.

b.map(x => a.push(x));

Después de ejecutar algunas pruebas de rendimiento en esto, es terriblemente lento, pero responde a la pregunta con respecto a no crear una nueva matriz. Concat es significativamente más rápido, incluso jQuery lo $.merge()grita.

https://jsperf.com/merge-arrays19b/1

elPastor
fuente
44
Debería usar en .forEachlugar de .mapsi no está usando el valor de retorno. Transmite mejor la intención de su código.
David
@david: a cada uno lo suyo. Prefiero la sinax de .mappero también podrías hacerlo b.forEach(function(x) { a.push(x)} ). De hecho, agregué eso a la prueba jsperf y es mucho más rápido que el mapa. Aún súper lento.
elPastor
3
Este no es un caso de preferencia. Estos métodos 'funcionales' tienen cada uno un significado específico asociado con ellos. Llamar .mapaquí se ve muy raro. Sería como llamar b.filter(x => a.push(x)). Funciona, pero confunde al lector al implicar que algo está sucediendo cuando no es así.
David
2
@elPastor debería valer la pena, porque otro tipo como yo se sentaría y se rascaría la nuca preguntándose qué más está sucediendo allí en su código que no puede ver. En la mayoría de los casos comunes, lo que importa es la claridad de la intención, no el rendimiento. Respeta el tiempo de aquellos que leen tu código, porque pueden ser muchos cuando solo eres uno. Gracias.
Dmytro Y.
1
@elPastor Sé exactamente qué .map()hace y ese es el problema. Este conocimiento me haría pensar que necesita la matriz resultante; de ​​lo contrario, sería bueno utilizarla .forEach()para que el lector tenga cuidado con los efectos secundarios de la función de devolución de llamada. Hablando estrictamente, su solución crea una matriz adicional de números naturales (resultados de push), mientras que la pregunta dice: "sin crear una nueva matriz".
Dmytro Y.