Skopiuj elementy tablicy do innej tablicy


916

Mam tablicę JavaScript, dataArrayktórą chcę wcisnąć do nowej tablicy newArray. Tyle że nie chcę newArray[0]być dataArray. Chcę wepchnąć wszystkie elementy do nowej tablicy:

var newArray = [];

newArray.pushValues(dataArray1);
newArray.pushValues(dataArray2);
// ...

lub nawet lepiej:

var newArray = new Array (
   dataArray1.values(),
   dataArray2.values(),
   // ... where values() (or something equivalent) would push the individual values into the array, rather than the array itself
);

Tak więc teraz nowa tablica zawiera wszystkie wartości poszczególnych tablic danych. Czy jest jakiś skrót, który jest pushValuesdostępny, więc nie muszę powtarzać każdego z osobna dataArray, dodając elementy jeden po drugim?



To powinna być odpowiedź davidwalsh.name/combining-js-arrays
starikovs

Odpowiedzi:


1246

Użyj funkcji concat , tak jak poniżej :

var arrayA = [1, 2];
var arrayB = [3, 4];
var newArray = arrayA.concat(arrayB);

Wartość newArraybędzie [1, 2, 3, 4]( arrayAi arrayBpozostanie niezmieniona; concattworzy i zwraca nową tablicę dla wyniku).



15
Zgadzam się, że wykonanie egzekucyjne jest bardzo miłe. ALE czyż konkat nie jest dokładnie w tym celu powiązany z tablicami? To powinno być standardowe . A może są inne lepsze rzeczy związane z concat? I może być powolny tylko z powodu złej implementacji silnika JS przeglądarki lub gdziekolwiek go używasz? Można to naprawić pewnego dnia. Wybrałbym łatwość obsługi kodu zamiast hacky optymalizacji prędkości. Hmm ....
Bitterblue,

3
Właśnie porównałem sytuację: concat vs. push.apply. Google Chrome: Szybki (concat = zwycięzca) Opera: szybki (concat = zwycięzca) IE: wolniej (concat = zwycięzca) Firefox: powolny (push.apply = zwycięzca, jeszcze 10 razy wolniej niż concat Chrome) ... mówić o złej implementacji silnika JS .
Bitterblue,

15
W jaki sposób łączenie dwóch tablic jest przyjętą odpowiedzią na to, jak popchnąć jedną w drugą ?! To dwie różne operacje.
kaqqao

@kaqqao, ponieważ pushnie spowoduje spłaszczenia tablicy wartości. concatosiąga to, czego wymaga pytanie.
WiseGuyEh

644

Pod warunkiem, że tablice nie są duże (patrz zastrzeżenie poniżej), możesz użyć push()metody tablicy, do której chcesz dołączyć wartości. push()może przyjmować wiele parametrów, dzięki czemu można użyć tej apply()metody do przekazania tablicy wartości, które mają zostać przekazane jako lista parametrów funkcji. Ma to tę zaletę, concat()że zamiast dodawać nowe elementy do tablicy zamiast tworzyć nową tablicę.

Wydaje się jednak, że w przypadku dużych tablic (rzędu 100 000 lub więcej członków) ta sztuczka może się nie powieść . W przypadku takich tablic lepszym rozwiązaniem jest użycie pętli. Szczegółowe informacje można znaleźć na stronie https://stackoverflow.com/a/17368101/96100 .

var newArray = [];
newArray.push.apply(newArray, dataArray1);
newArray.push.apply(newArray, dataArray2);

Możesz uogólnić to na funkcję:

function pushArray(arr, arr2) {
    arr.push.apply(arr, arr2);
}

... lub dodaj go do Arrayprototypu:

Array.prototype.pushArray = function(arr) {
    this.push.apply(this, arr);
};

var newArray = [];
newArray.pushArray(dataArray1);
newArray.pushArray(dataArray2);

... lub emuluj oryginalną push()metodę, dopuszczając wiele parametrów, wykorzystując fakt, że concat()np. push()pozwala na wiele parametrów:

Array.prototype.pushArray = function() {
    this.push.apply(this, this.concat.apply([], arguments));
};

var newArray = [];
newArray.pushArray(dataArray1, dataArray2);

Oto wersja ostatniego przykładu oparta na pętli, odpowiednia dla dużych tablic i wszystkich głównych przeglądarek, w tym IE <= 8:

Array.prototype.pushArray = function() {
    var toPush = this.concat.apply([], arguments);
    for (var i = 0, len = toPush.length; i < len; ++i) {
        this.push(toPush[i]);
    }
};

9
Uwaga: newArray.push.apply(newArray, dataArray1);daje to samo, coArray.prototype.push.applay(newArray,dataArra1);

Czy jest to zgodne ze starszymi przeglądarkami?
Damien Ó Ceallaigh,


1
@ DamienÓCeallaigh: Tak. Wszystko to jest kompatybilne z przeglądarkami wracającymi do IE 6, a nawet wcześniejszymi.
Tim Down

To uosobienie złych praktyk programistycznych. Array.prototype.concatnie jest złe, raczej jest po prostu źle zrozumiane, a czasem niewłaściwie używane. W tych okolicznościach jest to tylko niezrozumiane, ale nie nadużywane.
Jack Giffin,

444

Dodam jeszcze jedną odpowiedź „na przyszłość”

W ECMAScript 6 możesz użyć składni Spread :

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

console.log(arr1)

Składnia rozprzestrzeniania się nie jest jeszcze dostępna we wszystkich głównych przeglądarkach. Bieżącą kompatybilność można znaleźć w tej (stale aktualizowanej) tabeli zgodności .

Możesz jednak użyć składni rozprzestrzeniania w Babel.js .

edytować:

Zobacz odpowiedź Jacka Giffina poniżej, aby uzyskać więcej komentarzy na temat wydajności. Wydaje się, że konkat jest jeszcze lepszy i szybszy niż operator spreadu.


7
Możesz także użyć operatora rozkładania, jeśli używasz TypeScript. Jeśli celujesz w ES5, kompiluje się do newArray.apply(newArray, dataArray1).
AJ Richardson

5
Uwaga: jeśli potrzebujesz wyniku w trzeciej tablicy (a więc nie modyfikujesz arr1, jak początkowo wydawało się to wymagać pytania), możesz zrobić newArray = [... arr1, ... arr2]
dim

Och, nie wiedziałem tego. Dzięki. Dodałem komentarz do edycji.
Karel Bílek,

Podobne do tego, jak wtedy będzie działać concat, z dodatkową zaletą bycia trwałym (mutowanie oryginalnej tablicy).
Rob

@robertmylne Operator rozkładania oczywiście nie modyfikuje oryginalnej tablicy, a zamiast tego tworzy nową kopię wszystkich rozproszonych treści tablic.
Jack Giffin,

145

Znalazłem elegancki sposób z MDN

var vegetables = ['parsnip', 'potato'];
var moreVegs = ['celery', 'beetroot'];

// Merge the second array into the first one
// Equivalent to vegetables.push('celery', 'beetroot');
Array.prototype.push.apply(vegetables, moreVegs);

console.log(vegetables); // ['parsnip', 'potato', 'celery', 'beetroot']

Lub możesz użyć spread operatorfunkcji ES6:

let fruits = [ 'apple', 'banana'];
const moreFruits = [ 'orange', 'plum' ];

fruits.push(...moreFruits); // ["apple", "banana", "orange", "plum"]

1
Nie o to pytano. Pytanie dotyczy sposobu utworzenia nowej tablicy za każdym razem, a nie modyfikacji starej tablicy.
Jack Giffin,

13
var a=new Array('a','b','c');
var b=new Array('d','e','f');
var d=new Array('x','y','z');
var c=a.concat(b,d)

Czy to rozwiązuje twój problem?


13

Najprostsze wydaje mi się:

var newArray = dataArray1.slice();
newArray.push.apply(newArray, dataArray2);

Ponieważ „push” przyjmuje zmienną liczbę argumentów, możesz użyć applymetody pushfunkcji do wypchnięcia wszystkich elementów innej tablicy. Konstruuje wywołanie push, używając pierwszego argumentu (tutaj „newArray”) jako „this”, a elementów tablicy jako pozostałych argumentów.

sliceW pierwszym piśmie dostaje kopię pierwszej tablicy, więc nie modyfikować.

Aktualizacja Jeśli używasz wersji javascript z dostępnym plasterkiem, możesz uprościć pushwyrażenie do:

newArray.push(...dataArray2)

1
Jest również wspomniany tutaj w MDN jako przykład dla „Scalenia dwóch tablic”
Wilt

12

Poniższa funkcja nie ma problemu z długością tablic i działa lepiej niż wszystkie sugerowane rozwiązania:

function pushArray(list, other) {
    var len = other.length;
    var start = list.length;
    list.length = start + len;
    for (var i = 0; i < len; i++ , start++) {
        list[start] = other[i];
    }
}

niestety, jspref odmawia przyjęcia moich zgłoszeń, więc oto wyniki przy użyciu benchmark.js

        Name            |   ops/sec   |  ± %  | runs sampled
for loop and push       |      177506 |  0.92 | 63
Push Apply              |      234280 |  0.77 | 66
spread operator         |      259725 |  0.40 | 67
set length and for loop |      284223 |  0.41 | 66

gdzie

dla pętli i push jest:

    for (var i = 0, l = source.length; i < l; i++) {
        target.push(source[i]);
    }

Push Zastosuj:

target.push.apply(target, source);

operator rozrzutu:

    target.push(...source);

i wreszcie „ustawiona długość i pętla” to powyższa funkcja


To pytanie szuka sposobu utworzenia nowej tablicy za każdym razem, a nie modyfikacji istniejącej tablicy.
Jack Giffin,

10

𝗥𝗲𝘀𝗲𝗮𝗿𝗰𝗵 𝗔𝗻𝗱 𝗥𝗲𝘀𝘂𝗹𝘁𝘀

W rzeczywistości przeprowadzany jest test wydajności w jsperf i sprawdzanie niektórych rzeczy w konsoli. Do badań wykorzystano stronę irt.org . Poniżej znajduje się zbiór wszystkich tych źródeł razem oraz przykładowa funkcja na dole.

╔═══════════════╦══════╦═════════════════╦════════ ═══════╦═════════╦══════════╗
║ Metoda onKoccat║slice i push.apply ║ push.apply x2 ║ ForLoop ║Spread ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ mOps / s ║179 ║104 ║ 76 ║ 81 ║28 ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ Rzadkie tablice ║ TAK! N Tylko pokrojone ║ nie ║ Może 2   ║ nie ║
║ przechowywane rzadkie ║ ║ tablica (1. arg) ║ ║ ║ ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ Wsparcie ║ MSIE 4 ║ MSIE 5.5 ║ MSIE 5.5 ║ MSIE 4 ║ Krawędź 12 ║
║ ( źródło ) ║NNav 4║NNav 4.06 ║ NNav 4.06 ║ NNav 3 ║ MSIE  NNav ║
╠═══════════════╬══════╬═════════════════╬════════ ═══════╬═════════╬══════════╣
║ Akty podobne do tablic║nie ║ Tylko popychane ║ TAK! ║ TAK! F Jeśli masz ║
║ jak tablica ║ ║ tablica (2. arg) ║ ║ ║iterator 1   ║
╚═══════════════╩══════╩═════════════════╩════════ ═══════╩═════════╩══════════╝
1 Jeśli obiekt podobny do tablicy nie ma właściwości Symbol.iterator , spróbuj
  rozprzestrzenienie spowoduje wyjątek.
2 Zależy od kodu. Poniższy przykładowy kod „TAK” zachowuje rzadkość.
function mergeCopyTogether(inputOne, inputTwo){
   var oneLen = inputOne.length, twoLen = inputTwo.length;
   var newArr = [], newLen = newArr.length = oneLen + twoLen;
   for (var i=0, tmp=inputOne[0]; i !== oneLen; ++i) {
        tmp = inputOne[i];
        if (tmp !== undefined || inputOne.hasOwnProperty(i)) newArr[i] = tmp;
    }
    for (var two=0; i !== newLen; ++i, ++two) {
        tmp = inputTwo[two];
        if (tmp !== undefined || inputTwo.hasOwnProperty(two)) newArr[i] = tmp;
    }
    return newArr;
}

Jak widać powyżej, argumentowałbym, że Concat prawie zawsze jest drogą do osiągnięcia zarówno wydajności, jak i zdolności do zachowania rzadkości zapasowych macierzy. Następnie, dla podobnych do tablicy (takich jak DOMNodeLists, jak document.body.children), zaleciłbym użycie pętli for, ponieważ jest ona zarówno 2. najbardziej wydajną, jak i jedyną inną metodą, która zachowuje rzadkie tablice. Poniżej szybko przejrzymy, co oznaczają rzadkie tablice i tablice-lubi wyjaśnić zamieszanie.

𝗧𝗵𝗲 𝗙𝘂𝘁𝘂𝗿𝗲

Na początku niektórzy ludzie mogą myśleć, że jest to przypadek i że producenci przeglądarek w końcu zaczną optymalizować Array.prototype.push, aby być wystarczająco szybkim, aby pokonać Array.prototype.concat. ŹLE! Array.prototype.concat zawsze będzie szybszy (przynajmniej w zasadzie), ponieważ jest to zwykłe kopiowanie i wklejanie danych. Poniżej znajduje się uproszczony perswaado-wizualny schemat tego, jak może wyglądać implementacja tablicy 32-bitowej (pamiętaj, że prawdziwe implementacje są dużo bardziej skomplikowane)

Bajt ║ Dane tutaj
═════╬═══════════
0x00 ║ int nonNumericPropertiesLength = 0x00000000
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int długość = 0x00000001
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int wartośćIndeks = 0x00000000
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ int valueType = JS_PRIMITIVE_NUMBER
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid
0x00 ║ wartość uintptr_t Wskaźnik = 0x38d9eb60 (lub gdziekolwiek jest w pamięci)
0x01 ║ ibid
0x02 ║ ibid
0x03 ║ ibid

Jak widać powyżej, wszystko, co musisz zrobić, aby skopiować coś takiego, jest prawie tak proste, jak skopiowanie bajtu po bajcie. Dzięki Array.prototype.push.apply jest to znacznie więcej niż zwykłe kopiowanie i wklejanie danych. „.Apply” musi sprawdzić każdy indeks w tablicy i przekonwertować go na zestaw argumentów przed przekazaniem go do Array.prototype.push. Następnie Array.prototype.push musi dodatkowo przydzielać więcej pamięci za każdym razem, a (w przypadku niektórych implementacji przeglądarki) może nawet przeliczyć niektóre dane wyszukiwania pozycji dla rzadkości.

Alternatywnym sposobem myślenia o tym jest to. Pierwszy z nich to duży stos zszytych ze sobą papierów. Druga tablica źródłowa to także kolejny duży stos papierów. Czy byłoby to dla ciebie szybsze

  1. Idź do sklepu, kup wystarczającą ilość papieru potrzebną na kopię każdej tablicy źródłowej. Następnie umieść stosy papieru z każdej tablicy źródłowej w maszynie kopiującej i zszyj uzyskane dwie kopie razem.
  2. Idź do sklepu, kup wystarczającą ilość papieru na jedną kopię pierwszej tablicy źródłowej. Następnie ręcznie skopiuj tablicę źródłową na nowy papier, wypełniając puste puste miejsca. Następnie wróć do sklepu, kup wystarczającą ilość papieru na drugi układ źródłowy. Następnie przejdź przez drugą tablicę źródłową i skopiuj ją, upewniając się, że nie ma pustych przerw w kopii. Następnie zszyj wszystkie skopiowane dokumenty razem.

W powyższej analogii opcja nr 1 reprezentuje Array.prototype.concat, a nr 2 reprezentuje Array.prototype.push.apply. Przetestujmy to z podobnym JSperf, różniącym się tylko tym, że ten testuje metody na rzadkich tablicach, a nie na stałych tablicach. Można go znaleźć tutaj .

Dlatego opieram swój przypadek, że przyszłość wydajności dla tego konkretnego przypadku użycia nie leży w Array.prototype.push, ale raczej w Array.prototype.concat.

𝗖𝗹𝗮𝗿𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝘀

𝗦𝗽𝗮𝗿𝗲 𝗔𝗿𝗿𝗮𝘆𝘀

Gdy po prostu brakuje niektórych członków tablicy. Na przykład:

// This is just as an example. In actual code, 
// do not mix different types like this.
var mySparseArray = [];
mySparseArray[0] = "foo";
mySparseArray[10] = undefined;
mySparseArray[11] = {};
mySparseArray[12] =  10;
mySparseArray[17] = "bar";
console.log("Length:   ", mySparseArray.length);
console.log("0 in it:  ", 0 in mySparseArray);
console.log("arr[0]:   ", mySparseArray[0]);
console.log("10 in it: ", 10 in mySparseArray);
console.log("arr[10]   ", mySparseArray[10]);
console.log("20 in it: ", 20 in mySparseArray);
console.log("arr[20]:  ", mySparseArray[20]);

Alternatywnie, javascript pozwala łatwo inicjować zapasowe tablice.

var mySparseArray = ["foo",,,,,,,,,,undefined,{},10,,,,,"bar"];

𝗔𝗿𝗿𝗮𝘆-𝗟𝗶𝗸𝗲𝘀

Tablicowy to obiekt, który ma przynajmniej lengthwłaściwość, ale nie został zainicjowany przy pomocy new Arraylub []; Na przykład poniższe obiekty są klasyfikowane jako tablicowe.

{0: „foo”, 1: „bar”, długość: 2}
document.body.children
nowy Uint8Array (3)
  • Jest to podobne do tablicy, ponieważ chociaż jest to tablica (n) (typowana), wymuszenie jej na tablicę zmienia konstruktor.
(function () {return arguments}) ()

Obserwuj, co się dzieje, używając metody, która wymusza zamiany tablic na tablice takie jak plasterek.

  • UWAGA: Złą praktyką jest wywoływanie wycinka argumentów funkcji ze względu na wydajność.

Obserwuj, co się dzieje, używając metody, która nie zmusza tablic do tablic takich jak concat.


9

Za pomocą JavaScript ES6 możesz użyć operatora ... jako operatora rozkładania, który zasadniczo przekształci tablicę w wartości. Następnie możesz zrobić coś takiego:

const myArray = [1,2,3,4,5];
const moreData = [6,7,8,9,10];

const newArray = [
  ...myArray,
  ...moreData,
];

Chociaż składnia jest zwięzła, nie wiem, jak to działa wewnętrznie i jakie są konsekwencje wydajności dla dużych tablic.


2
Jeśli spojrzysz na to, jak Babel to przekształca , zobaczysz, że nie powinno to być wolniejsze niż użycie Array.push.applytechniki.
emil.c

1
@JackGiffin Mówiłem właśnie o tym, o czym wspomniał Ryan, że nie wie, jak to działa wewnętrznie i jakie są implikacje dotyczące wydajności, tak naprawdę nie sugerowałem takiego podejścia. W każdym razie wykonałeś bardzo dobrą robotę na swojej odpowiedzi, miłe badania, zawsze dobrze jest znać takie szczegóły.
emil.c

9

Oto sposób ES6

var newArray = [];
let dataArray1 = [1,2,3,4]
let dataArray2 = [5,6,7,8]
newArray = [...dataArray1, ...dataArray2]
console.log(newArray)

Powyższa metoda jest przydatna w większości przypadków i przypadków, których nie należy rozważać concat, ponieważ masz sto tysięcy elementów w tablicach.

    let dataArray1 = [1,2,3,4]
    let dataArray2 = [5,6,7,8]
    let newArray = dataArray1.concat(dataArray2);
    console.log(newArray)


8

Istnieje wiele odpowiedzi na temat Array.prototype.push.apply . Oto wyraźny przykład:

var dataArray1 = [1, 2];
var dataArray2 = [3, 4, 5];
var newArray = [ ];
Array.prototype.push.apply(newArray, dataArray1); // newArray = [1, 2]
Array.prototype.push.apply(newArray, dataArray2); // newArray = [1, 2, 3, 4, 5]
console.log(JSON.stringify(newArray)); // Outputs: [1, 2, 3, 4, 5]

Jeśli masz składnię ES6:

var dataArray1 = [1, 2];
var dataArray2 = [3, 4, 5];
var newArray = [ ];
newArray.push(...dataArray1); // newArray = [1, 2]
newArray.push(...dataArray2); // newArray = [1, 2, 3, 4, 5]
console.log(JSON.stringify(newArray)); // Outputs: [1, 2, 3, 4, 5]


4

Jeśli chcesz zmodyfikować oryginalną tablicę, możesz rozłożyć i wcisnąć:

var source = [1, 2, 3];
var range = [5, 6, 7];
var length = source.push(...range);

console.log(source); // [ 1, 2, 3, 5, 6, 7 ]
console.log(length); // 6

Jeśli chcesz się upewnić, że w sourcetablicy znajdują się tylko elementy tego samego typu (na przykład nie mieszając liczb i ciągów znaków), użyj TypeScript.

/**
 * Adds the items of the specified range array to the end of the source array.
 * Use this function to make sure only items of the same type go in the source array.
 */
function addRange<T>(source: T[], range: T[]) {
    source.push(...range);
}

2

Mamy dwie tablice a i b. kod, który tu zrobił to tablica, wartość jest wypychana do tablicy b.

let a = [2, 4, 6, 8, 9, 15]

function transform(a) {
    let b = ['4', '16', '64']
    a.forEach(function(e) {
        b.push(e.toString());
    });
    return b;
}

transform(a)

[ '4', '16', '64', '2', '4', '6', '8', '9', '15' ]

1
Proszę nie tylko kod pocztowy jako odpowiedź. Wyjaśnij, co robi kod i jak rozwiązuje problem.
Patrick Hund

0

Spróbuj tego:

var arrayA = [1, 2];
var arrayB = [3, 4];
var newArray = arrayB.reduce((pre, cur) => [...pre, ...cur], arrayA);
console.log(newArray)


-3

To jest działający kod i działa dobrze:

var els = document.getElementsByTagName('input'), i;
var invnum = new Array();
var k = els.length;
for(i = 0; i < k; i++){invnum.push(new Array(els[i].id,els[i].value))}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.