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.
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: array.slice
2: użycie for i zrobienie tego ‘ręcznie’
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?
Nie patrzcie na innych, podawajcie tyle ile sie da. Zapewne bede patrzyl na jasnosc tlumaczenia, przyklady, kod, wiec czym lepiej tym wieksze szanse ;-)
Aha, i tylko jeden komentarz sie liczy. Nie bede uznawal kolejnych.
ile czasu ?
Krótka piłka – do czwartku :)
Czwartek 23:59 zamykam ;-)
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ść :)
Niezbyt dobry pomysł z umieszczaniem odpowiedzi w komentarzach bo coraz to następne osoby nie zadadzą sobie wiele trudu ;)
Fakt, komentarze z odpowiedziami będą widoczne dopiero w czwartek.
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
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.
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.
o.length = a.lengthjest 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 ;-)
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.
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.
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.
Every przerwie wewnątrzną pętlę jeśli callback dla któregokolwiek elementu zwróci false. No to zwracamy true.
Some – odwrotnie – przerwie iterację, gdy callback dla dowolnego elementu zwróci true, więc zwracamy false.
Celowo pominąłem metodę Array.filter. Ogólnie, można ją wykorzystać w taki sposób
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.
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ć
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.
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.
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.
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.
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);
}
[...] 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 [...]
Rzeczywiście komentarz Rafała pozamiatał wszystko :>