Jak rozszerzyć istniejącą tablicę JavaScript o inną tablicę, bez tworzenia nowej tablicy

970

Wydaje się, że nie ma sposobu na rozszerzenie istniejącej tablicy JavaScript o inną tablicę, tj. Na emulację extendmetody Pythona .

Chcę osiągnąć następujące cele:

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

Wiem, że istnieje a.concat(b)metoda, ale tworzy ona nową tablicę zamiast po prostu rozszerzać pierwszą. Chciałbym algorytm, który działa skutecznie, gdy ajest znacznie większy niż b(tj. Taki, który nie kopiuje a).

Uwaga: To nie jest kopia jak dołączyć coś do tablicy? - celem jest dodanie całej zawartości jednej tablicy do drugiej i zrobienie tego „na miejscu”, tzn. bez kopiowania wszystkich elementów rozszerzonej tablicy.

DzinX
źródło
5
Z @ komentarzu szczoteczka w sprawie odpowiedzi: a.push(...b). Jest to koncepcja podobna do najwyższej odpowiedzi, ale zaktualizowana do ES6.
tscizzle

Odpowiedzi:

1497

.pushMetoda może trwać wiele argumentów. Możesz użyć operatora rozkładania, aby przekazać wszystkie elementy drugiej tablicy jako argumenty do .push:

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

Jeśli twoja przeglądarka nie obsługuje ECMAScript 6, możesz .applyzamiast tego użyć :

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

A może uważasz, że to jest jaśniejsze:

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

Pamiętaj, że wszystkie te rozwiązania zakończą się niepowodzeniem z błędem przepełnienia stosu, jeśli tablica bjest zbyt długa (problemy zaczynają się od około 100 000 elementów, w zależności od przeglądarki).
Jeśli nie możesz zagwarantować, że bjest wystarczająco krótki, powinieneś użyć standardowej techniki opartej na pętli opisanej w drugiej odpowiedzi.

DzinX
źródło
3
Myślę, że to najlepszy wybór. Wszystko inne będzie wymagało iteracji lub innego wysiłku zastosowania ()
Peter Bailey,
44
Ta odpowiedź nie powiedzie się, jeśli „b” (tablica, którą należy rozszerzyć) jest duże (według moich testów> około 150000 wpisów w Chrome). Powinieneś użyć pętli for, lub jeszcze lepiej użyć wbudowanej funkcji „forEach” na „b”. Zobacz moją odpowiedź: stackoverflow.com/questions/1374126/…
jcdude 10.10.2013
35
@Deqing: pushMetoda Array może przyjmować dowolną liczbę argumentów, które są następnie wypychane na tył tablicy. Więc a.push('x', 'y', 'z')jest ważne wezwanie, które rozciągają się aod 3 elementów. applyjest metodą dowolnej funkcji, która pobiera tablicę i używa jej elementów tak, jakby wszystkie zostały podane jawnie jako elementy pozycyjne funkcji. Tak a.push.apply(a, ['x', 'y', 'z'])będzie również rozszerzyć wachlarz przez 3 elementów. Dodatkowo applyprzyjmuje kontekst jako pierwszy argument (przekazujemy go aponownie, aby dołączyć a).
DzinX,
Uwaga: w tym scenariuszu pierwsza wartość zwracana nie jest [1,2,3,4,5], ale 5, a następnie == [1,2,3,4,5] później.
jawo
1
Jeszcze inna nieco mniej kłopotliwe (i nie dłużej) inwokacja będzie: [].push.apply(a, b).
niry
259

Aktualizacja 2018 : Lepszą odpowiedź jest nowsza jeden z kopalni : a.push(...b). Nie głosuj więcej na ten temat, ponieważ tak naprawdę nigdy nie odpowiedział na pytanie, ale był to hack z 2015 roku wokół pierwszego trafienia w Google :)


Dla tych, którzy po prostu szukali „Rozszerzenia tablicy JavaScript” i znaleźli się tutaj, możesz bardzo dobrze użyć Array.concat.

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

Concat zwróci kopię nowej tablicy, ponieważ początkowy wątek nie chciał. Ale może ci to nie przeszkadzać (z pewnością w przypadku większości zastosowań będzie to w porządku).


Do tego jest też trochę dobrego cukru ECMAScript 6 w postaci operatora spreadu:

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

(Kopiuje również.)

odinho - Velmont
źródło
43
Pytanie wyraźnie brzmiało: „bez tworzenia nowej tablicy?”
Wilt
10
@Wilt: To prawda, odpowiedź mówi jednak dlaczego :) To było pierwsze trafienie w Google od dłuższego czasu. Również inne rozwiązania były brzydkie, chciały czegoś idealnego i miłego. Chociaż obecnie arr.push(...arr2)jest nowsza i lepsza oraz poprawna technicznie odpowiedź na to pytanie.
odinho
7
Słyszę cię, ale szukałem „javascript append array bez tworzenia nowej tablicy” , to przykro mi widzieć, że 60 razy wysoko oceniana odpowiedź, która jest wysoko, nie odpowiada na rzeczywiste pytanie;) Nie głosowałem, ponieważ wyjaśniłeś to w swojej odpowiedzi.
Wilt
202

Powinieneś użyć techniki opartej na pętli. Inne odpowiedzi na tej stronie oparte na użyciu .applymogą się nie powieść w przypadku dużych tablic.

Dość zwarta implementacja oparta na pętli to:

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

Następnie możesz wykonać następujące czynności:

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

Odpowiedź DzinX (przy użyciu push.apply) i inne .applyoparte na nim metody zawodzą, gdy tablica, którą dodajemy, jest duża (testy pokazują, że dla mnie duża jest> około 150 000 wpisów w Chrome i> 500 000 wpisów w Firefox). Możesz zobaczyć ten błąd występujący w tym jsperf .

Wystąpił błąd, ponieważ rozmiar stosu wywołań został przekroczony, gdy „Function.prototype.apply” jest wywoływany z dużą tablicą jako drugim argumentem. (MDN ma uwagę na temat niebezpieczeństw związanych z przekroczeniem wielkości stosu wywołań przy użyciu Function.prototype.apply - patrz sekcja zatytułowana „Zastosuj i funkcje wbudowane”).

Aby porównać szybkość z innymi odpowiedziami na tej stronie, sprawdź ten jsperf (dzięki EaterOfCode). Implementacja oparta na pętli jest podobna pod względem szybkości do używania Array.push.apply, ale zwykle jest nieco wolniejsza niż Array.slice.apply.

Co ciekawe, jeśli dodawana macierz jest rzadka, forEachpowyższa metoda oparta na rzadkości może skorzystać z rzadkości i przewyższyć .applymetody oparte; sprawdź ten jsperf, jeśli chcesz to przetestować sam.

Nawiasem mówiąc, nie ulegaj pokusie (tak jak ja!) Dalszego skrócenia implementacji forEach do:

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

ponieważ powoduje to śmieciowe wyniki! Dlaczego? Ponieważ Array.prototype.forEachudostępnia trzy argumenty funkcji, którą wywołuje - są to: (element_value, element_index, source_array). Wszystkie zostaną forEachprzekazane do pierwszej tablicy dla każdej iteracji, jeśli użyjesz „forEach (this.push, this)”!

jcdude
źródło
1
ps, aby sprawdzić, czy tablica other_array naprawdę jest tablicą, wybierz jedną z opisanych tutaj opcji: stackoverflow.com/questions/767486/…
jcdude
6
Dobra odpowiedź. Nie byłem świadomy tego problemu. Przytoczyłem twoją odpowiedź tutaj: stackoverflow.com/a/4156156/96100
Tim Down
2
.push.applyjest w rzeczywistości znacznie szybszy niż .forEachw wersji 8, najszybsza jest nadal pętla wbudowana.
Benjamin Gruenbaum,
1
@BenjaminGruenbaum - czy możesz zamieścić link do niektórych wyników, które to pokazują? O ile widzę z wyników zebranych na stronie jsperf pod koniec tego komentarza, użycie .forEachjest szybsze niż .push.applyw Chrome / Chromium (we wszystkich wersjach od wersji 25). Nie byłem w stanie przetestować v8 w izolacji, ale jeśli tak, proszę połączyć swoje wyniki. Zobacz jsperf: jsperf.com/array-extending-push-vs-concat/5
jcdude
1
@jcdude twój jsperf jest nieprawidłowy. tak jak wszystkie elementy w tablicy undefined, więc .forEachpomiń je, dzięki czemu będzie najszybszy. .splicejest właściwie najszybszy ?! jsperf.com/array-extending-push-vs-concat/18
EaterOfCode
152

Najbardziej elegancki wydaje mi się w tych dniach:

arr1.push(...arr2);

Artykuł MDN na temat operatora spreadu wspomina o tym miłym, słodkim sposobie w ES2015 (ES6):

Lepsze pchnięcie

Przykład: push jest często używane do wypychania tablicy na koniec istniejącej tablicy. W ES5 jest to często wykonywane jako:

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

W ES6 z spreadem staje się to:

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

Zauważ, że arr2nie może być ogromne (trzymaj poniżej 100 000 przedmiotów), ponieważ stos wywołań przepełnia się, zgodnie z odpowiedzią jcdude.

odinho - Velmont
źródło
1
Tylko ostrzeżenie przed błędem, który właśnie popełniłem. Wersja ES6 jest bardzo bliska arr1.push(arr2). Może to być taki problem, arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)że wynikiem jest tablica tablic arr1 == [['a','b','d']]zamiast dwóch tablic razem. Łatwo jest popełnić błąd. Z tego powodu wolę twoją drugą odpowiedź poniżej. stackoverflow.com/a/31521404/4808079
Seph Reed
Jest wygodna w użyciu, .concatże drugim argumentem nie może być tablica. Można to osiągnąć przez połączenie operatora spreadu z concat, np.arr1.push(...[].concat(arrayOrSingleItem))
Steven Pribilinskiy,
Jest to nowoczesny, elegancki Javascript dla przypadków, w których tablica docelowa nie jest pusta.
timbo
Czy to jest wystarczająco szybkie?
Roel
@RoelDelosReyes: Tak.
odinho
46

Najpierw kilka słów o apply()JavaScript, aby zrozumieć, dlaczego go używamy:

apply()Sposób wywołuje funkcję z daną thiswartością i argumentów przedstawionych w tablicy.

Push oczekuje listy elementów do dodania do tablicy. apply()Sposób przyjmuje jednak oczekiwanych argumentów do połączenia funkcji jako macierzy. To pozwala nam łatwo łączyć pushelementy jednej tablicy w drugą za pomocą wbudowanej push()metody.

Wyobraź sobie, że masz te tablice:

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

i po prostu zrób to:

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

Wynik będzie:

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

To samo można zrobić w ES6 za pomocą operatora spreadu („ ...”) w następujący sposób:

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

Krótsze i lepsze, ale obecnie nie w pełni obsługiwane we wszystkich przeglądarkach.

Również jeśli chcesz przenieść wszystko z tablicy bdo a, opróżniając bproces, możesz to zrobić:

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

a wynik będzie następujący:

a = [1, 2, 3, 4, 5, 6, 7];
b = [];
Alireza
źródło
1
czy while (b.length) { a.push(b.shift()); }prawda?
LarsW,
37

Jeśli chcesz użyć jQuery, jest $ .merge ()

Przykład:

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

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

Jules Colle
źródło
1
Jak mogę znaleźć implementację (w kodzie źródłowym) jQuery.merge()?
ma11hew28,
Zajęło mi to 10 minut - jQuery jest open source, a źródło jest opublikowane na Github. Poszukałem „scalenia” i dalej zlokalizowałem coś, co wyglądało na definicję funkcji scalania w pliku core.js, patrz: github.com/jquery/jquery/blob/master/src/core.js#L331
rano
19

Podoba mi się a.push.apply(a, b)metoda opisana powyżej, a jeśli chcesz, zawsze możesz utworzyć funkcję biblioteki w ten sposób:

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

i użyj tego w ten sposób

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

a.append(b)
Pizzaiola Gorgonzola
źródło
6
Nie należy używać metody push.apply, ponieważ może ona spowodować przepełnienie stosu (a zatem i niepowodzenie), jeśli argument „tablica” jest dużą tablicą (np.> ~ 150000 wpisów w Chrome). Powinieneś użyć „array.forEach” - patrz moja odpowiedź: stackoverflow.com/a/17368101/1280629
jcdude
12

Można to zrobić za pomocą splice():

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

Ale chociaż jest brzydszy, nie jest szybszy niż push.apply, przynajmniej nie w Firefoksie 3.0.

Rafał Dowgird
źródło
9
Znalazłem to samo, łączenie nie zapewnia ulepszeń wydajności do wypychania każdego elementu, dopóki około 10 000 tablic elementów jsperf.com/splice-vs-push
Drew
1
+1 Dzięki za dodanie tego i porównania wydajności, zaoszczędziłem wysiłku na testowaniu tej metody.
jjrv
1
Użycie splice.apply lub push.apply może się nie powieść z powodu przepełnienia stosu, jeśli tablica b jest duża. Są także wolniejsze niż pętla „for” lub „forEach” - zobacz ten jsPerf: jsperf.com/array-extending-push-vs-concat/5 i moją odpowiedź stackoverflow.com/questions/1374126/…
jcdude
4

To rozwiązanie działa dla mnie (przy użyciu operatora rozprzestrzeniania 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
źródło
1

Możesz utworzyć polifill dla rozszerzenia, jak mam poniżej. Dodanie do tablicy; w miejscu i zwrócić się, aby można było połączyć inne metody.

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

Pan Polywhirl
źródło
0

Łączenie odpowiedzi ...

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
źródło
9
Nie, nie ma takiej potrzeby. Pętla for korzystająca z forEach jest szybsza niż użycie push.apply i działa bez względu na długość tablicy, o którą ma zostać przedłużona. Spójrz na moją poprawioną odpowiedź: stackoverflow.com/a/17368101/1280629 W każdym razie, skąd wiesz, że 150000 jest właściwą liczbą do użycia we wszystkich przeglądarkach? To jest krówka.
jcdude
lol. moja odpowiedź jest nierozpoznawalna, ale wydaje się być kombinacją innych znalezionych w tym czasie - tj. podsumowaniem. bez obaw
zCoder
0

Kolejne rozwiązanie do połączenia więcej niż dwóch tablic

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

Następnie zadzwoń i wydrukuj jako:

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

Dane wyjściowe będą: Array [1, 2, 3, 4, 5, 6, 7]

użytkownik3126309
źródło
-1

Odpowiedź jest bardzo prosta.

>>> 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 działa bardzo podobnie do konkatenacji ciągów JavaScript. Zwróci kombinację parametru, który umieściłeś w funkcji concat na końcu tablicy, na której wywołujesz funkcję. Najważniejsze jest to, że musisz przypisać zwróconą wartość do zmiennej, aby się zgubiła. Na przykład

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
źródło
2
Jak wyraźnie stwierdzono w dokumentacji dla Array.prototype.concat()MDN , zwróci to nową tablicę , nie będzie ona dołączana do istniejącej tablicy, jak wyraźnie wymagało OP.
Wilt
-2

Użyj Array.extendzamiast Array.push> 150 000 rekordów.

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
źródło
-6

Super prosty, nie liczy na operatorów spreadów ani nie stosuje się, jeśli to jest problem.

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

Po uruchomieniu kilku testów wydajności jest to bardzo powolne, ale odpowiada na pytanie, czy nie należy tworzyć nowej tablicy. Concat jest znacznie szybszy, nawet jQuery $.merge()go wyrzuca.

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

elPastor
źródło
4
Powinieneś użyć .forEachzamiast, .mapjeśli nie używasz wartości zwracanej. Lepiej oddaje intencje twojego kodu.
David
@david - każdemu z nich. Wolę synx, .mapale możesz też b.forEach(function(x) { a.push(x)} ). W rzeczywistości dodałem to do testu jsperf i jest to włos szybszy niż mapa. Wciąż bardzo wolno.
elPastor
3
To nie jest przypadek preferencji. Każda z tych „funkcjonalnych” metod ma określone znaczenie. Dzwonienie .maptutaj wygląda bardzo dziwnie. To byłoby jak dzwonienie b.filter(x => a.push(x)). Działa, ale dezorientuje czytelnika, sugerując, że coś się dzieje, gdy tak nie jest.
David
2
@elPastor powinno być warte twojego czasu, ponieważ ktoś inny jak ja siedziałby i drapał się po karku, zastanawiając się, co jeszcze dzieje się w twoim kodzie, którego nie widzi. W większości zwykłych przypadków liczy się jasność intencji, a nie wydajność. Szanuj czas tych, którzy czytają Twój kod, ponieważ może być ich wiele, gdy jesteś jednym z nich. Dzięki.
Dmytro Y.
1
@elPastor Wiem dokładnie, co .map()robi i na tym polega problem. Ta wiedza sprawiłaby, że pomyślałem, że potrzebujesz wynikowej tablicy, w przeciwnym razie dobrze byłoby użyć, .forEach()aby czytelnik był ostrożny w odniesieniu do skutków ubocznych funkcji wywołania zwrotnego. Ściśle mówiąc, twoje rozwiązanie tworzy dodatkową tablicę liczb naturalnych (wyniki push), podczas gdy pytanie brzmi: „bez tworzenia nowej tablicy”.
Dmytro Y.