Comment étendre un tableau JavaScript existant avec un autre tableau, sans créer un nouveau tableau

974

Il ne semble pas y avoir de moyen d'étendre un tableau JavaScript existant avec un autre tableau, c'est-à-dire d'émuler la extendméthode de Python .

Je veux atteindre les objectifs suivants:

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

Je sais qu'il existe une a.concat(b)méthode, mais elle crée un nouveau tableau au lieu d'étendre simplement le premier. Je voudrais un algorithme qui fonctionne efficacement lorsqu'il aest beaucoup plus grand que b(c'est-à-dire qui ne copie pas a)

Remarque: Ce n'est pas un doublon de Comment ajouter quelque chose à un tableau? - le but ici est d'ajouter tout le contenu d'un tableau à l'autre, et de le faire "sur place", c'est-à-dire sans copier tous les éléments du tableau étendu.

DzinX
la source
5
De @ commentaire de brosse à dents sur une réponse: a.push(...b). Son concept est similaire à la réponse du haut, mais mis à jour pour ES6.
tscizzle

Réponses:

1501

La .pushméthode peut prendre plusieurs arguments. Vous pouvez utiliser l' opérateur d'étalement pour passer tous les éléments du deuxième tableau comme arguments pour .push:

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

Si votre navigateur ne prend pas en charge ECMAScript 6, vous pouvez utiliser à la .applyplace:

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

Ou peut-être, si vous pensez que c'est plus clair:

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

Veuillez noter que toutes ces solutions échoueront avec une erreur de débordement de pile si le tableau best trop long (le problème commence à environ 100 000 éléments, selon le navigateur).
Si vous ne pouvez pas garantir que bc'est assez court, vous devez utiliser une technique standard basée sur la boucle décrite dans l'autre réponse.

DzinX
la source
3
Je pense que c'est votre meilleur pari. Tout autre élément impliquera une itération ou un autre effort d'application ()
Peter Bailey
44
Cette réponse échouera si "b" (le tableau à étendre) est grand (> 150000 entrées environ dans Chrome selon mes tests). Vous devriez utiliser une boucle for, ou mieux utiliser la fonction intégrée "forEach" sur "b". Voir ma réponse: stackoverflow.com/questions/1374126/…
jcdude
35
@Deqing: La pushméthode de Array peut prendre n'importe quel nombre d'arguments, qui sont ensuite poussés à l'arrière du tableau. Il en a.push('x', 'y', 'z')va de même pour un appel valide qui s'étendra asur 3 éléments. applyest une méthode de n'importe quelle fonction qui prend un tableau et utilise ses éléments comme s'ils étaient tous donnés explicitement comme éléments positionnels à la fonction. Il a.push.apply(a, ['x', 'y', 'z'])faudrait donc également étendre le tableau de 3 éléments. De plus, applyprend un contexte comme premier argument (nous y passons à anouveau pour l'ajouter a).
DzinX
Remarque: dans ce scénario, la première valeur de retour n'est pas [1,2,3,4,5] mais 5 tandis qu'un == [1,2,3,4,5] ensuite.
jawo
1
Encore une autre un peu moins confus (et pas beaucoup plus) invocation serait: [].push.apply(a, b).
2016
260

Mise à jour 2018 : Une meilleure réponse est une plus récente de la mine : a.push(...b). Ne votez plus pour celui-ci, car il n'a jamais vraiment répondu à la question, mais c'était un hack de 2015 autour du premier hit sur Google :)


Pour ceux qui ont simplement recherché "JavaScript array extend" et sont arrivés ici, vous pouvez très bien les utiliser Array.concat.

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

Concat retournera une copie du nouveau tableau, comme le démarreur de thread ne le voulait pas. Mais vous pourriez ne pas vous en soucier (certainement pour la plupart des types d'utilisations, ce sera parfait).


Il y a aussi du bon sucre ECMAScript 6 pour cela sous la forme de l'opérateur de propagation:

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

(Il copie également.)

odinho - Velmont
la source
43
La question disait clairement: "sans créer un nouveau tableau?"
Flétrissement
10
@Wilt: C'est vrai, la réponse explique pourquoi cependant :) Cela a été la première fois sur Google depuis longtemps. De plus, les autres solutions étaient moches, voulaient quelque chose d'idéomatique et de bien. Bien que de nos jours le arr.push(...arr2)soit plus récent et meilleur et une réponse techniquement correcte (tm) à cette question particulière.
odinho - Velmont
7
Je vous entends, mais j'ai cherché "tableau d'ajout javascript sans créer de nouveau tableau" , alors il est triste de voir qu'une réponse votée 60 fois plus haut ne répond pas à la question;) je n'ai pas downvote, puisque vous l'avez précisé dans votre réponse.
Flétrissement du
@Wilt et moi avons recherché "js extend array with array" et sommes arrivés ici donc merci :)
Davide Il y a
202

Vous devez utiliser une technique basée sur une boucle. Les autres réponses de cette page basées sur l'utilisation .applypeuvent échouer pour les grands tableaux.

Une implémentation en boucle assez concise est:

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

Vous pouvez ensuite effectuer les opérations suivantes:

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

La réponse de DzinX (en utilisant push.apply) et d'autres .applyméthodes basées échouent lorsque le tableau que nous ajoutons est grand (les tests montrent que pour moi, grand est> 150 000 entrées environ dans Chrome et> 500 000 entrées dans Firefox). Vous pouvez voir cette erreur se produire dans ce jsperf .

Une erreur se produit car la taille de la pile d'appels est dépassée lorsque «Function.prototype.apply» est appelé avec un grand tableau comme deuxième argument. (MDN a une note sur les dangers de dépasser la taille de la pile d'appels en utilisant Function.prototype.apply - voir la section intitulée "Appliquer et fonctions intégrées".)

Pour une comparaison de vitesse avec d'autres réponses sur cette page, consultez ce jsperf (grâce à EaterOfCode). L'implémentation basée sur la boucle est similaire en vitesse à l'utilisation Array.push.apply, mais a tendance à être un peu plus lente que Array.slice.apply.

Fait intéressant, si le tableau que vous ajoutez est clairsemé, la forEachméthode basée ci-dessus peut tirer parti de la rareté et surpasser les .applyméthodes basées; consultez ce jsperf si vous voulez le tester par vous-même.

Soit dit en passant, ne soyez pas tenté (comme je l'étais!) De raccourcir encore la mise en œuvre de forEach pour:

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

car cela produit des résultats poubelles! Pourquoi? Parce que Array.prototype.forEachfournit trois arguments à la fonction qu'elle appelle - ce sont: (element_value, element_index, source_array). Tous ces éléments seront poussés sur votre premier tableau à chaque itération de forEachsi vous utilisez "forEach (this.push, this)"!

jcdude
la source
1
ps pour tester si other_array est vraiment un tableau, choisissez l'une des options décrites ici: stackoverflow.com/questions/767486/…
jcdude
6
Bonne réponse. Je n'étais pas au courant de ce problème. J'ai cité votre réponse ici: stackoverflow.com/a/4156156/96100
Tim Down
2
.push.applyest en fait beaucoup plus rapide qu'en .forEachv8, le plus rapide est toujours une boucle en ligne.
Benjamin Gruenbaum
1
@BenjaminGruenbaum - pourriez-vous publier un lien vers des résultats montrant cela? Pour autant que je puisse voir des résultats collectés sur le jsperf lié à la fin de ce commentaire, l'utilisation .forEachest plus rapide que .push.applydans Chrome / Chromium (dans toutes les versions depuis la v25). Je n'ai pas pu tester la v8 isolément, mais si vous l'avez, veuillez lier vos résultats. Voir jsperf: jsperf.com/array-extending-push-vs-concat/5
jcdude
1
@jcdude votre jsperf n'est pas valide. comme tous les éléments de votre tableau undefined, .forEachsautez-les, ce qui en fait le plus rapide. .spliceest en fait le plus rapide?! jsperf.com/array-extending-push-vs-concat/18
EaterOfCode
154

Je pense que le plus élégant de nos jours est:

arr1.push(...arr2);

L' article de MDN sur l'opérateur de spread mentionne cette belle manière sucrée dans ES2015 (ES6):

Une meilleure poussée

Exemple: push est souvent utilisé pour pousser un tableau à la fin d'un tableau existant. Dans ES5, cela se fait souvent comme suit:

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

Dans ES6 avec propagation, cela devient:

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

Notez que arr2cela ne peut pas être énorme (gardez-le sous environ 100 000 éléments), car la pile d'appels déborde, selon la réponse de jcdude.

odinho - Velmont
la source
1
Juste un avertissement d'une erreur que je viens de commettre. La version ES6 est très proche arr1.push(arr2). Cela peut être un problème comme arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)le résultat étant un tableau de tableaux arr1 == [['a','b','d']]plutôt que deux tableaux combinés. C'est une erreur facile à faire. Je préfère votre deuxième réponse ci-dessous pour cette raison. stackoverflow.com/a/31521404/4808079
Seph Reed
Il y a une commodité lors de l'utilisation, .concatc'est que le deuxième argument ne doit pas être un tableau. Ceci peut être réalisé en combinant l'opérateur de propagation avec concat, par exemplearr1.push(...[].concat(arrayOrSingleItem))
Steven Pribilinskiy
Il s'agit de Javascript moderne et élégant pour les cas où le tableau cible n'est pas vide.
timbo
Est-ce assez rapide?
Roel
@RoelDelosReyes: Oui.
odinho - Velmont
46

D'abord quelques mots sur apply()JavaScript pour comprendre pourquoi nous l'utilisons:

La apply()méthode appelle une fonction avec une thisvaleur donnée et des arguments fournis sous forme de tableau.

Push attend une liste d'éléments à ajouter au tableau. Le apply()procédé, cependant, prend les arguments attendus pour l'appel de fonction sous forme de tableau. Cela nous permet de facilement pushles éléments d'un tableau dans un autre tableau avec la push()méthode intégrée .

Imaginez que vous ayez ces tableaux:

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

et faites simplement ceci:

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

Le résultat sera:

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

La même chose peut être effectuée dans ES6 en utilisant l'opérateur d'étalement (" ...") comme ceci:

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

Plus court et meilleur mais pas entièrement pris en charge dans tous les navigateurs pour le moment.

De plus, si vous souhaitez tout déplacer du tableau bvers le avidage bdans le processus, vous pouvez le faire:

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

et le résultat sera le suivant:

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

Si vous souhaitez utiliser jQuery, il y a $ .merge ()

Exemple:

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

Résultat: a = [1, 2, 3, 4, 5]

Jules Colle
la source
1
Comment trouver l'implémentation (dans le code source) de jQuery.merge()?
ma11hew28
Cela m'a pris 10 minutes - jQuery est open source et la source est publiée sur Github. J'ai fait une recherche de "fusion" et plus loin, j'ai trouvé
amn
19

J'aime la a.push.apply(a, b)méthode décrite ci-dessus, et si vous le souhaitez, vous pouvez toujours créer une fonction de bibliothèque comme celle-ci:

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

et l'utiliser comme ça

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

a.append(b)
Pizzaiola Gorgonzola
la source
6
La méthode push.apply ne doit pas être utilisée car elle peut provoquer un débordement de pile (et donc échouer) si votre argument "tableau" est un grand tableau (par exemple> ~ 150000 entrées dans Chrome). Vous devez utiliser "array.forEach" - voir ma réponse: stackoverflow.com/a/17368101/1280629
jcdude
12

Il est possible de le faire en utilisant splice():

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

Mais bien qu'il soit plus laid, il n'est pas plus rapide que push.apply, du moins pas dans Firefox 3.0.

Rafał Dowgird
la source
9
J'ai trouvé la même chose, l'épissure n'apporte pas d'amélioration des performances pour pousser chaque élément jusqu'à environ 10 000 tableaux d'éléments jsperf.com/splice-vs-push
Drew
1
+1 Merci d'avoir ajouté ceci et la comparaison des performances, cela m'a évité de tester cette méthode.
jjrv
1
L'utilisation de splice.apply ou push.apply peut échouer en raison d'un débordement de pile si la baie b est grande. Ils sont également plus lents que d'utiliser une boucle "for" ou "forEach" - voir ce jsPerf: jsperf.com/array-extending-push-vs-concat/5 et ma réponse stackoverflow.com/questions/1374126/…
jcdude
4

Cette solution fonctionne pour moi (en utilisant l'opérateur de propagation d'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
la source
1

Vous pouvez créer un polyfill pour étendre comme je l'ai ci-dessous. Cela ajoutera au tableau; en place et se retourner, de sorte que vous pouvez enchaîner d'autres méthodes.

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

M. Polywhirl
la source
0

Combiner les réponses ...

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
la source
9
Non, ce n'est pas nécessaire. Une boucle for utilisant forEach est plus rapide que l'utilisation de push.apply et fonctionne quelle que soit la longueur du tableau à étendre. Jetez un œil à ma réponse révisée: stackoverflow.com/a/17368101/1280629 Dans tous les cas, comment savez-vous que 150000 est le bon numéro à utiliser pour tous les navigateurs? Ceci est un fudge.
jcdude
lol. ma réponse est méconnaissable, mais semble être un combo d'autres trouvés à l'époque - c'est-à-dire un résumé. pas de soucis
zCoder
0

Une autre solution pour fusionner plus de deux baies

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

Ensuite, appelez et imprimez comme:

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

La sortie sera: Array [1, 2, 3, 4, 5, 6, 7]

user3126309
la source
-1

La réponse est 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 agit de manière très similaire à la concaténation de chaînes JavaScript. Il renverra une combinaison du paramètre que vous mettez dans la fonction concat à la fin du tableau sur lequel vous appelez la fonction. Le nœud est que vous devez affecter la valeur retournée à une variable ou elle se perd. Donc par exemple

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
la source
2
Comme clairement indiqué dans la documentation de Array.prototype.concat()MDN, cela retournera un nouveau tableau , il ne s'ajoutera pas au tableau existant comme OP le demandait clairement.
Flétrissement
-2

Utiliser Array.extendau lieu de Array.pushpour> 150 000 enregistrements.

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
la source
-6

Super simple, ne compte pas sur les opérateurs de spread ou ne s'applique pas, si c'est un problème.

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

Après avoir exécuté des tests de performances à ce sujet, c'est terriblement lent, mais répond à la question concernant la non-création d'un nouveau tableau. Concat est beaucoup plus rapide, même celui de jQuery $.merge().

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

elPastor
la source
4
Vous devez utiliser .forEachplutôt que .mapsi vous n'utilisez pas la valeur de retour. Il transmet mieux l'intention de votre code.
david
@david - à chacun ses goûts. Je préfère la synaxie de .mapmais vous pourriez aussi le faire b.forEach(function(x) { a.push(x)} ). En fait, j'ai ajouté cela au test jsperf et c'est un cheveu plus rapide que la carte. Toujours super lent.
elPastor
3
Ce n'est pas un cas de préférence. Ces méthodes «fonctionnelles» ont chacune une signification spécifique qui leur est associée. Appeler .mapici a l'air très bizarre. Ce serait comme appeler b.filter(x => a.push(x)). Cela fonctionne, mais cela déroute le lecteur en laissant entendre que quelque chose se passe alors que ce n'est pas le cas.
david
2
@elPastor, cela devrait valoir votre temps, car un autre gars comme moi s'asseyait et se grattait la nuque en se demandant ce qui se passait là-bas dans votre code qu'il ne pouvait pas voir. Dans la plupart des cas, c'est la clarté de l'intention qui compte, pas la performance. Veuillez respecter le temps de ceux qui lisent votre code, car ils peuvent être nombreux lorsque vous n'êtes qu'un. Merci.
Dmytro Y.
1
@elPastor Je sais exactement ce que .map()ça fait et c'est ça le problème. Cette connaissance me ferait penser que vous avez besoin du tableau résultant, sinon ce serait une bonne chose à utiliser .forEach()pour rendre le lecteur prudent quant aux effets secondaires de la fonction de rappel. À strictement parler, votre solution crée un tableau supplémentaire de nombres naturels (résultats de push), tandis que la question indique: "sans créer de nouveau tableau".
Dmytro Y.