Javascript - wstaw tablicę do innej tablicy

88

Jaki jest bardziej efektywny sposób wstawiania tablicy do innej tablicy.

a1 = [1,2,3,4,5];
a2 = [21,22];

newArray - a1.insertAt(2,a2) -> [1,2, 21,22, 3,4,5];

Iterowanie a2 za pomocą splice wygląda trochę fatalnie z punktu widzenia wydajności, jeśli tablica a2 jest duża.

Dzięki.

ic3
źródło
2
to nie jeden element, ale tablica, więc splot nie działa
ic3

Odpowiedzi:

180

Możesz użyć w splicepołączeniu z pewnymi applysztuczkami:

a1 = [1,2,3,4,5];
a2 = [21,22];

a1.splice.apply(a1, [2, 0].concat(a2));

console.log(a1); // [1, 2, 21, 22, 3, 4, 5];

W ES2015 + możesz zamiast tego użyć operatora spreadu, aby uczynić to nieco ładniejszym

a1.splice(2, 0, ...a2);
nickf
źródło
10
@icCube: Właśnie przeprowadziłem test porównawczy, porównując tę ​​metodę z odpowiedzią Patricka. Zaczyna się od a1 i a2, jak w przykładzie, i wstrzykuje zmienną a2 do a1 10 000 razy (tak, że na końcu otrzymujemy tablicę z 20 005 elementami). Ta metoda zajęła: 81 ms , metoda slice + concat zajęła 156 ms (testowana na Chrome 13) . Oczywiście wiąże się to ze standardowym zastrzeżeniem, że w większości przypadków czytelność będzie ważniejsza niż prędkość.
nickf
3
@nick: W chrome, jeśli tablice przechowują ponad 130000 elementów, applyzgłosi wyjątek przepełnienia stosu . jsfiddle.net/DcHCY Dzieje się również w jsPerf Wierzę w FF i IE, próg wynosi około 500 tys.
Nie,
dlaczego używasz Apply w przeciwieństwie do bezpośredniego wywoływania splice i przekazywania argumentów?
Peter P.
@ FrançoisWahl, to poważny problem, który ludzie często ignorują w swoich odpowiedziach. Wziąłem twój przykład i sprawiłem, że działał z elementami północnymi z 1M: stackoverflow.com/a/41466395/1038326
Gabriel Kohen
Właśnie to wypróbowałem (czerwiec 2017 r.) I najszybszym sposobem zarówno dla małych rozmiarów tablic, jak i dużych rozmiarów tablic (w Chrome, FF i Edge) wydaje się być target.slice(0, insertIndex).concat(insertArr, target.slice(insertIndex)); jsperf.com/inserting-an-array-within-an-array
Alex Dima
14

Na początku było źle. Powinienem był użyć concat()zamiast tego.

var a1 = [1,2,3,4,5],
    a2 = [21,22],
    startIndex = 0,
    insertionIndex = 2,
    result;    

result = a1.slice(startIndex, insertionIndex).concat(a2).concat(a1.slice(insertionIndex));

Przykład: http://jsfiddle.net/f3cae/1/

To wyrażenie używa slice(0, 2)[docs], aby zwrócić pierwsze dwa elementy a1(gdzie 0jest indeksem początkowym i 2jest elementem deleteCount, chociaż a1nie jest zmieniany).

Wynik pośredni :[1,2]

Następnie używa concat(a2)[docs], aby dołączyć a2na końcu [1,2].

Wynik pośredni : [1,2,21,22].

Następnie a1.slice(2)wywoływana jest w końcowym .concat()końcu tego wyrażenia, co sprowadza się do [1,2,21,22].concat(a1.slice(2)).

Wywołanie funkcji slice(2), mające dodatni argument w postaci liczby całkowitej, zwróci wszystkie elementy po drugim elemencie, licząc według liczb naturalnych (tak jak w przypadku jest pięć elementów, więc [3,4,5]zostanie zwrócona z a1). Innym sposobem na powiedzenie tego jest to, że argument indeksu pojedynczej liczby całkowitej wskazuje, a1.slice()od której pozycji w tablicy należy rozpocząć zwracanie elementów (indeks 2 jest trzecim elementem).

Wynik pośredni :[1,2,21,22].concat([3,4,5])

Wreszcie druga .concat()dodaje [3,4,5]na końcu [1,2,21,22].

Wynik :[1,2,21,22,3,4,5]

Zmiana może być kusząca Array.prototype, ale można po prostu rozszerzyć obiekt Array za pomocą dziedziczenia prototypowego i wstrzyknąć wspomniany nowy obiekt do swoich projektów.

Jednak dla tych, którzy żyją na krawędzi ...

Przykład: http://jsfiddle.net/f3cae/2/

Array.prototype.injectArray = function( idx, arr ) {
    return this.slice( 0, idx ).concat( arr ).concat( this.slice( idx ) );
};

var a1 = [1,2,3,4,5];
var a2 = [21,22];

var result = a1.injectArray( 2, a2 );
user113716
źródło
Otrzymuję a1 z 6 pozycjami nie 7 -> [1,2, [21,22], 3,4,5]
ic3
Pracuje ! wielkie dzięki, poczekajmy kilka dni, ale to najlepsza odpowiedź ... dziwne, że nie mają do tego funkcji js.
ic3
Podoba mi się ten jeden bardziej
Kimchi Man
Podsumowując, silnik JS musi zwrócić cztery tablice przed zakończeniem wyrażenia. Podoba mi się logika rozwiązania, ale może nie być optymalne ze względu na przestrzeń. Zgadzam się jednak, że jest to fajne i bardzo kompatybilne z różnymi przeglądarkami. Korzystanie z operatora rozprzestrzeniania się wiąże się z kosztami, ponieważ działa on na kolekcjach, ale na dłuższą metę mógłbym to zastąpić zwracaniem czterech tablic.
Anthony Rutledge
3

Rozprzestrzenianie operatora umożliwia wyrażenie zostać rozszerzony w miejscach, w których oczekuje się wiele argumentów (dla połączeń Function) lub więcej elementów (na literałach tablicy).

a2 = [21,22];
a1 = [1,2,...a2,3,4,5];//...a2 is use of spread operator
console.log(a1);

Hikmat Sijapati
źródło
3

Istnieje kilka naprawdę twórczych odpowiedzi na to pytanie. Oto proste rozwiązanie dla tych, którzy dopiero zaczynają przygodę z tablicami. W razie potrzeby można go zmusić do pracy aż do przeglądarek zgodnych z ECMAScript 3.

Dowiedz się czegoś o łączeniu, zanim zaczniesz.

Mozilla Developer Network: Array.prototype.splice ()

Najpierw poznaj dwie ważne formy .splice().

let a1 = [1,2,3,4],
    a2 = [1,2];

Metoda 1) Usuń elementy x (deleteCount), zaczynając od żądanego indeksu.

let startIndex = 0, 
    deleteCount = 2;

a1.splice(startIndex, deleteCount); // returns [1,2], a1 would be [3,4]

Metoda 2) Usuń elementy po żądanym indeksie początkowym do końca tablicy.

a1.splice(2); // returns [3,4], a1 would be [1,2]

Używając .splice(), celem może być podzielenie a1na układy głowy i ogona przy użyciu jednej z dwóch powyższych form.

Korzystając z metody nr 1, zwracana wartość byłaby głową i a1końcem.

let head = a1.splice(startIndex, deleteCount); // returns [1,2], a1 would be [3,4]

Teraz za jednym zamachem połącz głowę, ciało ( a2) i ogon

[].concat(head, a2, a1);

Tym samym rozwiązanie to bardziej przypomina rzeczywisty świat niż jakiekolwiek inne prezentowane do tej pory. Czy nie to byś zrobił z Legosem? ;-) Oto funkcja wykonywana przy użyciu metody nr 2.

/**
*@param target Array The array to be split up into a head and tail.
*@param body Array The array to be inserted between the head and tail.
*@param startIndex Integer Where to split the target array.
*/
function insertArray(target, body, startIndex)
{
    let tail = target.splice(startIndex); // target is now [1,2] and the head
    return [].concat(target, body, tail);
}

let newArray = insertArray([1, 2, 3, 4], ["a", "b"], 2); // [1, 2, "a", "b", 3, 4]

Krótszy:

/**
*@param target Array The array to be split up into a head and tail.
*@param body Array The array to be inserted between the head and tail.
*@param startIndex Integer Where to split the target array.
*/
function insertArray(target, body, startIndex)
{
    return [].concat(target, body, target.splice(startIndex));
}

Bezpieczniej:

/**
*@param target Array The array to be split up into a head and tail.
*@param body Array The array to be inserted between the head and tail.
*@param startIndex Integer Where to split the target array.
*@throws Error The value for startIndex must fall between the first and last index, exclusive.
*/
function insertArray(target, body, startIndex)
{
    const ARRAY_START = 0,
          ARRAY_END = target.length - 1,
          ARRAY_NEG_END = -1,
          START_INDEX_MAGNITUDE = Math.abs(startIndex);

    if (startIndex === ARRAY_START) {
        throw new Error("The value for startIndex cannot be zero (0).");
    }

    if (startIndex === ARRAY_END || startIndex === ARRAY_NEG_END) {
        throw new Error("The startIndex cannot be equal to the last index in target, or -1.");
    }

    if (START_INDEX_MAGNITUDE >= ARRAY_END) {
        throw new Error("The absolute value of startIndex must be less than the last index.");
    }

    return [].concat(target, body, target.splice(startIndex));
}

Zalety tego rozwiązania to:

1) W rozwiązaniu dominuje proste założenie - wypełnij pustą tablicę.

2) Nazewnictwo głowy, ciała i ogona jest naturalne.

3) Brak podwójnego wywołania .slice(). Żadnego krojenia.

4) Nie .apply(). Wysoce niepotrzebne.

5) Unika się łączenia metod.

6) Działa w ECMAScript 3 i 5 po prostu używając varzamiast letlub const.

** 7) Zapewnia, że ​​będzie głowa i ogon do uderzenia w ciało, w przeciwieństwie do wielu innych przedstawionych rozwiązań. Jeśli dodajesz tablicę przed lub po granicach, powinieneś przynajmniej użyć .concat()!!!!

Uwaga: użycie opearatora do rozprzestrzeniania ...sprawia, że ​​wszystko to jest znacznie łatwiejsze do wykonania.

Anthony Rutledge
źródło
2

Chciałem znaleźć sposób, aby to zrobić splice()bez iteracji: http://jsfiddle.net/jfriend00/W9n27/ .

a1 = [1,2,3,4,5];
a2 = [21,22];

a2.unshift(2, 0);          // put first two params to splice onto front of array
a1.splice.apply(a1, a2);   // pass array as arguments parameter to splice
console.log(a1);           // [1, 2, 21, 22, 3, 4, 5];

W postaci funkcji ogólnego przeznaczenia:

function arrayInsertAt(destArray, pos, arrayToInsert) {
    var args = [];
    args.push(pos);                           // where to insert
    args.push(0);                             // nothing to remove
    args = args.concat(arrayToInsert);        // add on array to insert
    destArray.splice.apply(destArray, args);  // splice it in
}
jfriend00
źródło
To również modyfikuje a2tablicę, co prawdopodobnie jest całkiem niepożądane. Nie musisz też przechodzić do punktu, w Array.prototypektórym masz już funkcję wycinania na a1lub a2.
nickf
Zdawałem sobie sprawę, że modyfikuje a2. OP może zdecydować, czy to jest problem, czy nie. Czy jest coś złego w chodzeniu do prototypu w celu uzyskania metody? Wydawało mi się to bardziej odpowiednie, ponieważ chciałem tylko metody, a obiekt był dodawany do apply()metody.
jfriend00
Cóż, nie, to dokładnie ta sama funkcja, ale jedna jest krótsza: Array.prototype.splicevs a1.splice:)
nickf
Dodano funkcję wstawiania tablicy ogólnego przeznaczenia.
jfriend00
.concatzwraca nową tablicę, nie modyfikuje istniejącej, jak np splice. Powinno byćargs = args.concat(arrayToInsert);
nickf
0
var a1 = [1,2,3,4,5];
var a2 = [21,22];

function injectAt(d, a1, a2) {
    for(var i=a1.length-1; i>=d; i--) {
        a1[i + a2.length] = a1[i];
    }
    for(var i=0; i<a2.length; i++) {
        a1[i+d] = a2[i];
    }
}

injectAt(2, a1, a2);

alert(a1);
Bhesh Gurung
źródło
0

Oto moja wersja bez specjalnych sztuczek:

function insert_array(original_array, new_values, insert_index) {
    for (var i=0; i<new_values.length; i++) {
        original_array.splice((insert_index + i), 0, new_values[i]);
    }
    return original_array;
}
arlomedia
źródło
W tym przypadku prostsze może być po prostu .reverse () tablicy new_values, zamiast zwiększać insert_index dla każdej iteracji. Oczywiście, gdy start_index ma wartość zero lub start_index - 1, wydajność tego rozwiązania jest słaba, w porównaniu do samego użycia .concat () bez żadnych jawnych pętli lub unshift () lub push () `z .apply().
Anthony Rutledge
0

Jeśli chcesz wstawić kolejną tablicę do tablicy bez tworzenia nowego, najprostszym sposobem jest użycie albo pushlub unshiftzapply

Na przykład:

a1 = [1,2,3,4,5];
a2 = [21,22];

// Insert a1 at beginning of a2
a2.unshift.apply(a2,a1);
// Insert a1 at end of a2
a2.push.apply(a2,a1);

To działa, ponieważ oba pushi unshiftprzyjmują zmienną liczbę argumentów. Bonus, możesz łatwo wybrać, z którego końca chcesz dołączyć tablicę!

RangerMauve
źródło
Robiąc, a1.concat(a2)albo a2.concat(a1)wymyśliłem coś łatwiejszego niż to, co sugerujesz. Jednak problem polega na wstawieniu tablicy między granicami tablicy, a nie wyłącznie na dodaniu tablicy na początku lub na końcu. ;-)
Anthony Rutledge
0

Jak wspomniano w innym wątku, powyższe odpowiedzi nie będą działać w bardzo dużych tablicach (200K elementów). Zobacz alternatywną odpowiedź dotyczącą łączenia i ręcznego wypychania: https://stackoverflow.com/a/41465578/1038326

Array.prototype.spliceArray = function(index, insertedArray) {
    var postArray = this.splice(index);
    inPlacePush(this, insertedArray);
    inPlacePush(this, postArray);

    function inPlacePush(targetArray, pushedArray) {
  // Not using forEach for browser compatability
        var pushedArrayLength = pushedArray.length;
        for (var index = 0; index < pushedArrayLength; index++) {
           targetArray.push(pushedArray[index]);
       }
    }
}
Gabriel Kohen
źródło