Wygraj wejściówkę na Falsy Values

Do wygrania wejściówka na mój workshop oraz konferencję. Pytanie: podaj jak najwięcej sposobów na sklonowanie tablicy w JavaScript. Odpowiedzi umieszczajcie w komentarzach.

Komentarze

1

1: array.slice
2: użycie for i zrobienie tego ‚ręcznie’

2

Pytanie techniczne: Skoro @Krzysztof podał dwa rozwiązania, to ja podając następne mam już punkty za tamte dwa, czy wolno napisać tylko jeden komentarz zawierający wyłącznie nowe odpowiedzi?

3

Nie patrzcie na innych, podawajcie tyle ile sie da. Zapewne bede patrzyl na jasnosc tlumaczenia, przyklady, kod, wiec czym lepiej tym wieksze szanse ;-)

4

Aha, i tylko jeden komentarz sie liczy. Nie bede uznawal kolejnych.

5

ile czasu ?

owca
6

Krótka piłka – do czwartku :)

7

Czwartek 23:59 zamykam ;-)

8

1: array.slice wywołując .slice(0)
2: pętla for po klonowanej tablicy

3: JSON.encode a potem JSON.decode
4: dokleić pustą

var foo=[1,2,3];
var q=foo.concat([]);

5: Przekazać klonowaną tablicę jako listę aprametrów do new Array()

var foo=[1,2,3];
var c=Array.apply(this,foo);

6: z łańcucha

var foo=[1,2,3];
var a=foo.valueOf().toString().split(',');

7: przekazując jako parametr do funkcji też się jakoś dało sklonować, ale w firebugu mi nie wyszło…

Nie mam w tym tygodniu czasu na większy risercz, więc tylko takie „off the top of my head”. Może będzie bonus za szybkość :)

9

Niezbyt dobry pomysł z umieszczaniem odpowiedzi w komentarzach bo coraz to następne osoby nie zadadzą sobie wiele trudu ;)

Bartek Ziółkowski
10

Fakt, komentarze z odpowiedziami będą widoczne dopiero w czwartek.

11

To moje odpowiedzi i tak już większość zainteresowanych widziała. Ale decyzję popieram, bo słuszna.

Ciekawostka odkryta przy okazji:
Internet twierdzi i moje eksperymenty potwierdzają, że
do new Array() nie da się podać tablicy argumentów tak jak do zwykłych funkcji przy pomocy apply(), bo nie ma gdzie wcisnąć operatora new.
Efekt zbliżony do tego da się uzyskać opakowując własną klasą, ale to już jest mnożenie kodu dookoła rozwiązań opisanych wcześniej (w niewidocznych postach:P) – i samo przekazanie tablicy jako tablicy argumentów nie jest ważne dla kopiowania, bo kopiowanie odbędzie się w kodzie konstruktora.

Świadomie podaję pomysł konkurencji, bo albo odkryją sposób, albo stracą czas – tak czy inaczej – zyskam :P

12

1.Utworzenie nowej tablicy i ręczne jej przepisywanie, np. nowa[1] = stara[1].
2.Przepisanie jej na podobnej zasadzie, ale w pętli for(var i=0; i<=stara.length-1; i++).
3.Utworzenie przez prototyp dla obiektu Array funkcji klonującej wykorzystującej pętlę for.
4.Jak wyżej, tylko, że z pętlą for…in.
5.Za pomocą pętli for…in, ale bez prototypu.
6.Chyba najprostszy sposób, nowa = stara.
Można by dodać jeszcze powyższe metody, ale działające od ostatniego indeksu, czyli mamy 11 sposobów.

Damian
13

Zadanie może wygląda na proste, ale trudność zależy od tego, co rozumiemy jako klona/kopię. Głównie chodzi mi tutaj o to czy akceptowany jest przypadek:
na wejściu [1,,,,5]
na wyjściu [1,undefined,undefined,undefined,5]

Główna różnica pomiędzy obydwoma polega na warunku

2 in input; // false
2 in output; // true

Zaczniemy klasycznie, prosta pętla. Czy to będzie for, while, for .. in, wszystko jedno. Sprowadza się do tego samego.

Aby zabezpieczyć się przed dodawaniem nieistniejących wartości, dodamy prostego IFa.

function cloneLoop (a) {
    var o = [];
    var l = a.length;
    o.length = l;
    for (var i = 0, l = a.length; i < l; i++) { // for, while i wszelkie inne pętle
        if (i in a) {
            o[i] = a[i];
        }
    } 
    return o;
}

o.length = a.length jest po to, żeby tablica wynikowa była odpowiedniej długości, nawet gdyby ostatnie indeksy tablicy wejściowej były puste [1,2,,,], przez co pominięte przez wstawionego IFa.

3 następne można uznać za jedyne słuszne ;-)

function cloneSlice (a) {
    return a.slice(0);
}
function cloneConcat (a) {
    return a.concat([]); // można w zasadzie pominąć []
}
function cloneConcat2 (a) {
    return [].concat(a);
}

Mamy jeszcze inne metody tablicowe. Wykorzystamy niektóre z nich
Array.push, wiadomo, wrzuca swoje argumenty na koniec tablicy. Więc wrzucimy elementy tablicy wejściowej. Pomocne będzie Function.apply, żeby spłaszczyć tablicę wejściową do listy argumentów funkcji. Metoda cierpi na to samo co Funkcja nr 1 bez instrukcji warunkowej.

function clonePush (a) {
    var o = [];
    o.push.apply(o, a);
    return o;
}

Podobnie jak Array.push, Array.unshift też pozwala wrzucić elementy do tablicy, ale wrzuca je na początek a nie koniec. Dla pustej tablicy nie robi to żadnej różnicy. Problem ten sam co w przypadku Array.push.

function cloneUnshift (a) {
    var o = [];
    o.unshift.apply(o, a);
    return o;
}

Ecmascript 5 oferuje nowe metody typu forEach, map, some, every, reduce, reduceRight. Wszystkie oczekują callbacka jako argument i wszystkie tworzą w ten sposób niejawną pętlę iterującą po tablicy. Zwykle dostajemy wartość z tablicy jako pierwszy argument callbacka i indeks tablicy jako drugi. Korzystamy z jednego albo obydwu. Array.map wydaje się być zaraz po Array.slice i Array.concat najsensowniejszym rozwiązaniem.

function cloneMap (a) {
    return a.map(function (v) {
        return v;
    });
}
function cloneForEach (a) {
    var o = [];
    o.length = a.length;
    a.forEach(function (v, i) {
        o[i] = v;
    });
    return o;
}

Every przerwie wewnątrzną pętlę jeśli callback dla któregokolwiek elementu zwróci false. No to zwracamy true.

function cloneEvery (a) {
    var o = [];
    o.length = a.length;
    a.every(function (v, i) {
        return o[i] = v, true;
    });
    return o;
}

Some – odwrotnie – przerwie iterację, gdy callback dla dowolnego elementu zwróci true, więc zwracamy false.

function cloneSome (a) {
    var o = [];
    o.length = a.length;
    a.some(function (v, i) {
        return o[i] = v, false;
    });
    return o;
}
function cloneReduce (o) {
    var o = [];
    o.length = a.length;
    a.reduce(function (a, b, i) {
        return o[i] = b;
    }, 0);
    return o;
}
function cloneReduceRight (o) {
    var o = [];
    o.length = a.length;
    a.reduceRight(function (a, b, i) {
        return o[i] = b;
    }, 0); // domyślna wartość jest konieczna inaczej zostanie zignorowany jeden z elementów
    return o;
}

Celowo pominąłem metodę Array.filter. Ogólnie, można ją wykorzystać w taki sposób

function cloneFilter (a) {
    return a.filter(function () { return true; });
}

Wszystko działa do czasu aż nie trafimy na tablicę wspomnianą na wstępie. Wynikiem będzie [1, 5]. Metoda ta zatem odpada.

Kombinując dalej, można wykorzystać funkcję konstruktora.

function cloneArrayConstructor (a) {
    return Array.apply(null, a);
}

Problem z tą funkcją polega na tym, że nie działa dla tablic jednoelementowych zawierających liczby.
Array.apply(null, [1]) utworzy pustą tablicę jednoelementową
Array.apply(null, [-1]) wyrzuci błąd
Odpada.

Można oczywiście się zabezpieczać

function cloneArrayConstructor (a) {
    var l = a.length, o;
    return o = l === 1 ? [a[0]] : Array.apply(null, a);
}

Dalej to już dziwne kombinacje. Wykorzystamy Array.splice podobnie jak Array.push. Bezsensowność tego rozwiązania polega na tym, że żeby utworzyć poprawną listę argumentów Array.splice(index, length, item0, item1, …) musimy użyć Array.concat, więc w zasadzie w środku tworzymy już klona tablicy wejściowej i dołączamy do niego [0, 0] na początku. Obecny jest też problem wspomniany na wstępie.

function cloneSplice (a) {
    var o = [];
    o.splice.apply(o, [0, 0].concat(a));
    return o;
}

OK, skończyły nam się metody tablicowe, które oczekiwały callbacka, więc zabieramy się za inne typy. Przykładowo String.replace też oczekuje callbacka.

function cloneStringReplace (a) {
    var o = [], i = 0, l = a.length;
    o.length = l;
    Array(l + 1).join('1').replace(/./g,function () {
        if (i in a) {
            o[i] = a[i];
        } 
        i++;
    });
    return o;
}

Powyższe działa w ten sposób, że tworzymy ciąg jedynek ‚111111…’ tak długi jak długa jest tablica wejściowa. Potem dajemy replace na każdej jedynce, dbając przy okazji o zwiększanie zmiennej licznika (i), żeby za każdym przebiegiem skakała o jedną wartość.

Można się też pozbyć konieczności ręcznego updateowania tej zmiennej pomocniczej. Wystarczy wykorzystać argumenty przekazywane do callbacka. Pierwszym jest dopasowany przez wyrażenie ciąg znaków, kolejne N elementów to zapamiętane, zgrupowane przez wyrażenie podciągi, zaś przedostatni to offset od początku stringa. Dopasowujemy po jednym znaku, więc offset będzie przesuwał się zawsze o jeden.

function cloneStringReplaceOffset (a) {
    var o = [], l = a.length;
    o.length = a.length;
    Array(l + 1).join('1').replace(/./g, function(m, i) {
        if (i in a) {
            o[i] = a[i];
        }
    });
    return o;
}

Poniższe to tylko desperacka próba innych rozwiązań. Mamy pętlę, która przesuwa RegExp.lastIndex o kolejne pozycje wraz z każdym wywołaniem RegExp.exec.

function cloneRegExpExecLastIndex (a) {
    var o = [],
        r = /./g,
        l = a.length,
        s = Array(l + 1).join('1');
    o.length = l;
    
    while (r.exec(s)) {
        var i = r.lastIndex - 1;  
        if (i in a) {
            o[i] = a[i];
        }
    }
    return o;
}
Rafał Kukawski
14

Nie wiem co sobie myślałem przy pisaniu o Array.filter. Nie można jej użyć w tradycyjny sposób, ale można oczywiście użyć jak użyłem Array.forEach i innych.

function cloneFilter (a) {
var o = [];
o.length = a.length;
a.filter(function (v, i) {
o[i] = v;
});
return o;
}

Można też rekursywnie przesuwać się o jeden indeks do przodu


function cloneRecursive (a) {
var o = [];
var l = a.length;
o.length = l;
return function (i) {
if (i < l) { // jeśli mamy jeszcze parę indeksów do końca tablicy
if (i in a) { // jeśli indeks istnieje w tablicy wejściowej
o[i] = a[i]; // kopiujemy
}
return arguments.callee(i + 1); // i szukamy następnego
}
return o; // nie ma więcej indeksów, wychodzimy
}(0);
}

Rafał Kukawski
15

[…] Konkurs na podanie jak największej liczby sposobów na klonowanie tablicy wygrał Zbyszek T. Warto zwrócić uwagę jednak na imponującą listę rozwiązań przysłaną przez Rafała Kukawskiego (który jednak wejściówkę już ma ;-)). 13/05 0 […]

16

Rzeczywiście komentarz Rafała pozamiatał wszystko :>

Dodaj komentarz

Dozwolone tagi: <blockquote>, <code>, <strong>