Como estender uma matriz JavaScript existente com outra matriz, sem criar uma nova matriz

974

Não parece haver uma maneira de estender uma matriz JavaScript existente com outra matriz, ou seja, emular o extendmétodo do Python .

Eu quero alcançar o seguinte:

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

Eu sei que existe um a.concat(b)método, mas ele cria uma nova matriz em vez de simplesmente estender a primeira. Eu gostaria de um algoritmo que funcione eficientemente quando afor significativamente maior que b(isto é, um que não copie a).

Nota: Esta não é uma duplicata de Como acrescentar algo a uma matriz? - o objetivo aqui é adicionar todo o conteúdo de uma matriz à outra e fazê-lo "no lugar", ou seja, sem copiar todos os elementos da matriz estendida.

DzinX
fonte
5
De @ comentário de escova de dentes em uma resposta: a.push(...b). O conceito é semelhante à resposta principal, mas atualizado para o ES6.
tscizzle

Respostas:

1501

O .pushmétodo pode receber vários argumentos. Você pode usar o operador spread para passar todos os elementos da segunda matriz como argumentos para .push:

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

Se o seu navegador não suportar o ECMAScript 6, você poderá usar .apply:

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

Ou talvez, se você acha mais claro:

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

Observe que todas essas soluções falharão com um erro de estouro de pilha se a matriz bfor muito longa (o problema começa em cerca de 100.000 elementos, dependendo do navegador).
Se você não pode garantir que bseja curto o suficiente, use uma técnica padrão baseada em loop descrita na outra resposta.

DzinX
fonte
3
Eu acho que essa é sua melhor aposta. Qualquer outra coisa que vai envolver iteração ou de outro esforço de aplicar ()
Peter Bailey
44
Essa resposta falhará se "b" (a matriz a ser expandida) for grande (> 150000 entradas aproximadamente no Chrome, de acordo com meus testes). Você deve usar um loop for ou, melhor ainda, a função "forEach" embutida em "b". Veja minha resposta: stackoverflow.com/questions/1374126/…
jcdude 10/10
35
@Deqing: o pushmétodo da matriz pode levar qualquer número de argumentos, que são empurrados para a parte de trás da matriz. O mesmo a.push('x', 'y', 'z')vale para uma chamada que se estenderá apor 3 elementos. applyé um método de qualquer função que pega uma matriz e usa seus elementos como se todos fossem dados explicitamente como elementos posicionais para a função. Assim a.push.apply(a, ['x', 'y', 'z'])também estenderia a matriz em 3 elementos. Além disso, applyassume um contexto como o primeiro argumento (passamos anovamente para o qual anexar a).
DzinX
Nota: neste cenário, o primeiro valor de retorno não é [1,2,3,4,5], mas 5, enquanto a == [1,2,3,4,5] depois.
Jawo
1
No entanto, outro um pouco menos confuso (e não muito mais tempo) invocação seria: [].push.apply(a, b).
niry
259

Atualização 2018 : Uma resposta melhor é a mais recente de uma mina : a.push(...b). Não vote mais nessa, pois ela nunca respondeu à pergunta, mas foi um hack de 2015 em torno do primeiro hit no Google :)


Para aqueles que simplesmente pesquisaram "JavaScript array extend" e chegaram aqui, você pode muito bem usar Array.concat.

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

O Concat retornará uma cópia da nova matriz, pois o iniciador de threads não desejava. Mas você pode não se importar (certamente, para a maioria dos usos, isso será bom).


Há também um bom açúcar ECMAScript 6 para isso na forma do operador de propagação:

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

(Ele também copia.)

odinho - Velmont
fonte
43
A pergunta dizia claramente: "sem criar uma nova matriz?"
Wilt
10
@ Wilt: Isso é verdade, a resposta diz o porquê: :) Isso foi atingido pela primeira vez no Google há muito tempo. Também as outras soluções eram feias, queriam algo ideomatic e agradável. Embora atualmente a arr.push(...arr2)resposta seja mais nova e melhor e uma resposta tecnicamente correta (tm) a essa pergunta em particular.
odinho - Velmont 16/16
7
Ouvi você, mas procurei por "javascript anexar matriz sem criar uma nova matriz" ; então, é triste ver que uma resposta com 60 vezes mais votos que é alta não responde à pergunta real;) não diminuiu o voto, desde que você deixou claro em sua resposta.
Wilt
202

Você deve usar uma técnica baseada em loop. Outras respostas nesta página baseadas no uso .applypodem falhar em matrizes grandes.

Uma implementação baseada em loop bastante concisa é:

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);
}

Você pode fazer o seguinte:

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

A resposta do DzinX (usando push.apply) e outros .applymétodos baseados falham quando a matriz que estamos anexando é grande (testes mostram que para mim grandes são> 150.000 entradas aproximadamente no Chrome e> 500.000 entradas no Firefox). Você pode ver esse erro ocorrendo neste jsperf .

Ocorre um erro porque o tamanho da pilha de chamadas é excedido quando 'Function.prototype.apply' é chamado com uma matriz grande como o segundo argumento. (MDN tem uma observação sobre os perigos de exceder o tamanho da pilha de chamadas usando Function.prototype.apply - consulte a seção "funções aplicáveis ​​e incorporadas".)

Para uma comparação de velocidade com outras respostas nesta página, consulte este jsperf (graças ao EaterOfCode). A implementação baseada em loop é semelhante em velocidade ao uso Array.push.apply, mas tende a ser um pouco mais lenta que Array.slice.apply.

Curiosamente, se a matriz que você está anexando é escassa, o forEachmétodo baseado acima pode tirar vantagem da escassez e superar os .applymétodos baseados; confira este jsperf se você quiser testar isso por si mesmo.

A propósito, não fique tentado (como eu estava!) A reduzir ainda mais a implementação do forEach para:

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

porque isso produz resultados de lixo! Por quê? Porque Array.prototype.forEachfornece três argumentos para a função que chama - estes são: (element_value, element_index, source_array). Tudo isso será enviado para sua primeira matriz para cada iteração forEachse você usar "forEach (this.push, this)"!

jcdude
fonte
1
ps Para testar se other_array é realmente um array, escolha uma das opções descritas aqui: stackoverflow.com/questions/767486/...
jcdude
6
Boa resposta. Eu não estava ciente desse problema. Eu citei sua resposta aqui: stackoverflow.com/a/4156156/96100
Tim Down
2
.push.applyé realmente muito mais rápido que .forEachna v8, o mais rápido ainda é um loop inline.
Benjamin Gruenbaum 25/05
1
@BenjaminGruenbaum - você poderia postar um link para alguns resultados mostrando isso? Tanto quanto eu posso ver pelos resultados coletados no jsperf vinculado no final deste comentário, o uso .forEaché mais rápido que .push.applyno Chrome / Chromium (em todas as versões desde a v25). Não pude testar a v8 isoladamente, mas se você tiver, vincule seus resultados. Veja jsperf: jsperf.com/array-extending-push-vs-concat/5
jcdude
1
@jcdude seu jsperf é inválido. como todos os elementos em sua matriz undefined, .forEachos ignorará, tornando-o o mais rápido. .spliceé realmente o mais rápido ?! jsperf.com/array-extending-push-vs-concat/18
EaterOfCode
153

Sinto que o mais elegante hoje em dia é:

arr1.push(...arr2);

O artigo da MDN sobre o operador de spread menciona essa maneira agradável e açucarada no ES2015 (ES6):

Um empurrão melhor

Exemplo: push é frequentemente usado para enviar uma matriz ao final de uma matriz existente. No ES5, isso geralmente é feito como:

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

No ES6 com spread, isso se torna:

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

Observe que arr2não pode ser enorme (mantenha-o abaixo de cerca de 100.000 itens), porque a pilha de chamadas transborda, conforme resposta do jcdude.

odinho - Velmont
fonte
1
Apenas um aviso de um erro que acabei de cometer. A versão ES6 está muito próxima arr1.push(arr2). Isso pode ser um problema, portanto arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)o resultado é uma matriz de matrizes, em arr1 == [['a','b','d']]vez de duas matrizes combinadas. É um erro fácil de cometer. Prefiro sua segunda resposta abaixo por esse motivo. stackoverflow.com/a/31521404/4808079
Seph Reed
Há uma conveniência ao usar .concato segundo argumento não deve ser uma matriz. Isso pode ser conseguido combinando o operador de spread com concat, por exemplo,arr1.push(...[].concat(arrayOrSingleItem))
Steven Pribilinskiy
É um Javascript moderno e elegante para casos em que a matriz de destino não está vazia.
timbó
Isso é rápido o suficiente?
Roel
@RoelDelosReyes: Sim.
odinho - Velmont 02/03
46

Primeiro, algumas palavras sobre apply()JavaScript para ajudar a entender por que usamos:

O apply()método chama uma função com um determinado thisvalor e os argumentos são fornecidos como uma matriz.

O Push espera uma lista de itens a serem adicionados à matriz. O apply()método, no entanto, aceita os argumentos esperados para a chamada de função como uma matriz. Isso nos permite facilmente pushos elementos de uma matriz em outra matriz com o push()método embutido .

Imagine que você tem estas matrizes:

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

e simplesmente faça o seguinte:

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

O resultado será:

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

O mesmo pode ser feito no ES6 usando o operador de spread (" ...") assim:

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

Mais curto e melhor, mas não totalmente suportado em todos os navegadores no momento.

Além disso, se você deseja mover tudo da matriz bpara o aesvaziamento bno processo, você pode fazer isso:

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

e o resultado será o seguinte:

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

Se você deseja usar o jQuery, há $ .merge ()

Exemplo:

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

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

Jules Colle
fonte
1
Como posso encontrar a implementação (no código fonte) de jQuery.merge()?
Ma11hew28 02/09
Levei 10 minutos - o jQuery é de código aberto e o código é publicado no Github. Fiz uma pesquisa por "mesclagem" e localizei o que parecia ser a definição da função de mesclagem no arquivo core.js, consulte: github.com/jquery/jquery/blob/master/src/core.js#L331
amn 27/02
19

Eu gosto do a.push.apply(a, b)método descrito acima e, se você quiser, pode sempre criar uma função de biblioteca como esta:

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

e use assim

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

a.append(b)
Pizzaiola Gorgonzola
fonte
6
O método push.apply não deve ser usado, pois pode causar um estouro de pilha (e, portanto, falhar) se o argumento "array" for um array grande (por exemplo,> ~ 150000 entradas no Chrome). Você deve usar "array.forEach" - veja minha resposta: stackoverflow.com/a/17368101/1280629
jcdude 14/10/2013
12

É possível fazer isso usando splice():

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

Mas apesar de ser mais feio, não é mais rápido que push.apply, pelo menos não no Firefox 3.0.

Rafał Dowgird
fonte
9
Eu encontrei a mesma coisa, emenda não fornece melhorias de desempenho para empurrar cada item até cerca de 10.000 matrizes elemento jsperf.com/splice-vs-push
de Drew
1
+1 Obrigado por adicionar isso e a comparação de desempenho, poupou-me o esforço de testar esse método.
Jjrv
1
O uso de splice.apply ou push.apply pode falhar devido a um estouro de pilha se a matriz b for grande. Eles também são mais lentos que o loop "for" ou "forEach" - veja este jsPerf: jsperf.com/array-extending-push-vs-concat/5 e minha resposta stackoverflow.com/questions/1374126/…
jcdude
4

Esta solução funciona para mim (usando o operador de propagação do 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
fonte
1

Você pode criar um polyfill para estender como eu tenho abaixo. Ele será adicionado à matriz; no local e retorne a si próprio, para que você possa encadear outros 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
fonte
0

Combinando as respostas ...

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
fonte
9
Não, não há necessidade de fazer isso. Um loop for usando forEach é mais rápido que usar push.apply e funciona independentemente do tamanho da matriz a ser estendida. Dê uma olhada na minha resposta revisada: stackoverflow.com/a/17368101/1280629 De qualquer forma, como você sabe que 150000 é o número certo para usar em todos os navegadores? Isso é um fudge.
jcdude
ri muito. minha resposta é irreconhecível, mas parece ser uma combinação de outras encontradas na época - ou seja, um resumo. Não se preocupe
zCoder
0

Outra solução para mesclar mais de duas matrizes

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]);
  }
 }
};

Em seguida, ligue e imprima como:

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

A saída será: Array [1, 2, 3, 4, 5, 6, 7]

user3126309
fonte
-1

A resposta é super simples.

>>> 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]

O Concat atua de maneira muito semelhante à concatenação de strings JavaScript. Ele retornará uma combinação do parâmetro que você colocou na função concat no final da matriz na qual você chama a função. O ponto crucial é que você deve atribuir o valor retornado a uma variável ou ele se perde. Então por exemplo

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
fonte
2
Conforme claramente indicado nos documentos Array.prototype.concat()do MDN, isso retornará uma nova matriz , ela não será anexada à matriz existente, como o OP estava pedindo claramente.
Wilt
-2

Use em Array.extendvez 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
fonte
-6

Super simples, não conta com operadores espalhados ou aplica, se isso é um problema.

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

Depois de executar alguns testes de desempenho, é muito lento, mas responde à pergunta em relação à não criação de uma nova matriz. O Concat é significativamente mais rápido, até o jQuery $.merge()grita.

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

elPastor
fonte
4
Você deve usar .forEache não .mapse não estiver usando o valor de retorno. Ele transmite melhor a intenção do seu código.
david
@ David - cada um na sua. Eu prefiro a sinaxia de, .mapmas você também pode fazer b.forEach(function(x) { a.push(x)} ). Na verdade, eu adicionei isso ao teste jsperf e é um cabelo mais rápido que o mapa. Ainda super lento.
elPastor 25/02/19
3
Este não é um caso de preferência. Cada um desses métodos 'funcionais' tem um significado específico associado a eles. Ligar .mapaqui parece muito estranho. Seria como ligar b.filter(x => a.push(x)). Funciona, mas confunde o leitor, implicando que algo está acontecendo quando não está.
david
2
@elPastor, vale a pena o seu tempo, porque outro cara como eu sentaria e coçaria a nuca dele, imaginando o que mais está acontecendo lá no seu código que ele não pode ver. Na maioria dos casos comuns, é a clareza de intenção que importa, não o desempenho. Respeite o tempo daqueles que leem seu código, pois podem ser muitos quando você é apenas um. Obrigado.
Dmytro Y.
1
@elPastor Eu sei exatamente o que .map()faz e esse é o problema. Esse conhecimento me faria pensar que você precisa da matriz resultante, caso contrário, seria uma boa coisa usar .forEach()o cauteloso leitor sobre os efeitos colaterais da função de retorno de chamada. A rigor, sua solução cria uma matriz adicional de números naturais (resultados de push), enquanto a pergunta afirma: "sem criar uma nova matriz".
Dmytro Y.