Kombinace polí JavaScriptu

Toto je rychlý jednoduchý příspěvek o technikách JavaScriptu. Probereme různé metody pro kombinování/slučování dvou polí JS a klady a zápory každého přístupu.

Začněme scénářem:

var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var b = [ "foo", "bar", "baz", "bam", "bun", "fun" ];

Jednoduché zřetězení a a b by samozřejmě bylo:

[
   1, 2, 3, 4, 5, 6, 7, 8, 9,
   "foo", "bar", "baz", "bam" "bun", "fun"
]

concat(..)

Nejběžnější přístup je:

var c = a.concat( b );

a; // [1,2,3,4,5,6,7,8,9]
b; // ["foo","bar","baz","bam","bun","fun"]

c; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

Jak můžete vidět, c je zcela nový array což představuje kombinaci dvou a a b pole, zbývá a a b nedotčený. Jednoduché, že?

Co když a je 10 000 položek a b je 10 000 položek? c je nyní 20 000 položek, což v podstatě představuje zdvojnásobení využití paměti z a a b .

"Žádný problém!", řeknete. Právě jsme zrušili nastavení a a b takže se sbírají odpadky, ne? Problém vyřešen!

a = b = null; // `a` and `b` can go away now

Meh. Jen za pár malých array s, to je v pořádku. Ale pro velké array s nebo opakovaným pravidelným opakováním tohoto procesu nebo prací v prostředí s omezenou pamětí, zanechá mnoho přání.

Opakované vkládání

Dobře, pojďme připojit jeden array 's na druhý pomocí Array#push(..) :

// `b` onto `a`
for (var i=0; i < b.length; i++) {
    a.push( b[i] );
}

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

b = null;

Nyní a má výsledek obou původních a plus obsah b .

Lepší pro paměť, zdá se.

Ale co když a byl malý a b byl srovnatelně opravdu velký? Z důvodů paměti i rychlosti budete pravděpodobně chtít použít menší a na přední stranu b spíše než delší b na konec a . Žádný problém, stačí nahradit push(..) s unshift(..) a smyčka v opačném směru:

// `a` into `b`:
for (var i=a.length-1; i >= 0; i--) {
    b.unshift( a[i] );
}

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

a = null;

Funkční triky

Unpro naštěstí for smyčky jsou nevzhledné a hůře se udržují. Můžeme udělat něco lepšího?

Zde je náš první pokus s použitím Array#reduce :

// `b` onto `a`:
a = b.reduce( function(coll,item){
    coll.push( item );
    return coll;
}, a );

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

// or `a` into `b`:
b = a.reduceRight( function(coll,item){
    coll.unshift( item );
    return coll;
}, b );

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

Array#reduce(..) a Array#reduceRight(..) jsou pěkné, ale jsou trochu neohrabané. ES6 => arrow-functions je mírně zeštíhlí, ale stále to vyžaduje volání funkce na položku, což je nešťastné.

Co třeba:

// `b` onto `a`:
a.push.apply( a, b );

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

// or `a` into `b`:
b.unshift.apply( b, a );

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

To je mnohem hezčí, že!? Zejména od unshift(..) přístup se zde nemusí obávat opačného řazení jako v předchozích pokusech. Operátor šíření ES6 bude ještě hezčí:a.push( ...b ) nebo b.unshift( ...a ) .

Věci však nejsou tak růžové, jak by se mohlo zdát. V obou případech předání buď a nebo b na apply(..) druhý argument 's (nebo prostřednictvím ... operátor spread) znamená, že pole je rozloženo jako argumenty funkce.

Prvním hlavním problémem je, že efektivně zdvojnásobujeme velikost (dočasně, samozřejmě!) přidávané věci tím, že v podstatě zkopírujeme její obsah do zásobníku pro volání funkce. Kromě toho mají různé motory JS různá omezení v závislosti na implementaci, pokud jde o počet argumentů, které lze předat.

Pokud tedy array přidávání obsahuje milion položek, téměř jistě byste překročili velikost zásobníku povoleného pro tento push(..) nebo unshift(..) volání. Fuj. Bude to fungovat dobře pro několik tisíc prvků, ale musíte si dávat pozor, abyste nepřekročili rozumně bezpečný limit.

Poznámka: Můžete zkusit to samé s splice(..) , ale budete mít stejné závěry jako u push(..) / unshift(..) .

Jednou z možností by bylo použít tento přístup, ale dávkovat segmenty v maximální bezpečné velikosti:

function combineInto(a,b) {
    var len = a.length;
    for (var i=0; i < len; i=i+5000) {
        b.unshift.apply( b, a.slice( i, i+5000 ) );
    }
}

Počkejte, vracíme se zpět, pokud jde o čitelnost (a možná i výkon!). Přestaňme, než se vzdáme všech našich dosavadních zisků.

Přehled

Array#concat(..) je osvědčený a pravdivý přístup ke kombinaci dvou (nebo více!) polí. Ale skryté nebezpečí spočívá v tom, že se vytváří nové pole namísto úpravy jednoho ze stávajících.

Existují možnosti, které se modifikují na místě, ale mají různé kompromisy.

Vzhledem k různým výhodám a nevýhodám je možná nejlepší ze všech možností (včetně dalších, které nejsou zobrazeny) reduce(..) a reduceRight(..) .

Ať už si vyberete cokoli, pravděpodobně je dobré kriticky přemýšlet o své strategii slučování polí, než ji považovat za samozřejmost.