Obsah
1. Underscore aneb další knihovna pro funkcionální programování v JavaScriptu
2. Mori versus Underscore: dva různé přístupy k podobné problematice
3. Základní funkce pro „funkcionální“ práci s poli
6. Různé varianty funkce range
11. Čtvrtý demonstrační příklad
12. Zploštění vnořených polí, kombinace prvků z různých polí atd.
14. Repositář s demonstračními příklady
1. Underscore aneb další knihovna pro funkcionální programování v JavaScriptu
V postupně vznikajícím miniseriálu o různých knihovnách, frameworcích a transpřekladačích určených pro stále populárnější programovací jazyk JavaScript jsme se prozatím zabývali čtyřmi zajímavými projekty. Nejprve jsme si popsali takzvaný transpřekladač (transcompiler) pojmenovaný lua2js, který transformuje kód z programovacího jazyka Lua do JavaScriptu. Dále jsme se zmínili o virtuálním stroji jazyka Lua naprogramovaném v JavaScriptu, který se jmenuje lua.vm.js.
Dalším projektem byl opět transpřekladač nazvaný Wisp, který slouží pro transformaci zdrojového kódu z jazyka syntaxí podobného LISPu, Scheme či Clojure do JavaScriptu, přičemž transformovaný kód je stále velmi dobře čitelný (u některých dalších transpřekladačů toto pravidlo neplatí). Posledním prozatím popsaným projektem je knihovna pojmenovaná Mori, která do JavaScriptu přináší perzistentní a neměnitelné datové struktury používané v jazyku Clojure, resp. přesněji řečeno ClojureScriptu. Ve skutečnosti Mori implementaci perzistentních datových struktur přímo z ClojureScriptu přebírá a jen nepatrně je upravuje a přidává snadno použitelné JavaScriptové API.
Pro podrobnější informace o výše zmíněných projektech lua2js, lua.vm.js, Wisp a Mori se stačí podívat na následující pětici článků, které již zde na Rootu vyšly:
- Programovací jazyk Lua v roli skriptovacího jazyka pro WWW stránky (popis projektů lua2js a lua.vm.js)
- Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp (1.část)
- Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp (2.část)
- Projekt Mori aneb perzistentní datové struktury pro JavaScript
- Projekt Mori aneb perzistentní datové struktury pro JavaScript (dokončení)
2. Mori versus Underscore: dva různé přístupy k podobné problematice
Již zmíněný projekt Mori je sice postaven na robustních knihovnách ClojureScriptu, v nichž jsou implementovány perzistentní seznamy, vektory, fronty, množiny a mapy, to však může být v některých případech i jeho nevýhoda, protože i „minifikovaná“ verze této knihovny je relativně velká (konkrétně 185 kB). Alternativu, která je dnes s velkou pravděpodobností mezi javascriptisty více populární, představuje například knihovna nazvaná Underscore či též Underscore.js. Mimochodem, jméno této knihovny je odvozeno od toho, že se ke všem funkcím přistupuje přes objekt pojmenovaný jednoduše „_“ (kombinace s JQuery apod. je tedy možná, zdrojový kód pak začne být pěkně přeplněn různými divnými symboly :-).
Knihovna Underscore je navržena poměrně minimalistickým způsobem, což konkrétně znamená, že současná „minifikovaná“ verze této knihovny má velikost necelých šestnácti kilobajtů (první verze měla dokonce jen čtyři kilobajty). Tyto rozdíly se sice mohou zdát malé, ovšem u komplikovanější aplikace (navíc přenášené někdy přes pomalé a drahé připojení) se počítá každý ušetřený kilobajt (ale možná jen jsem ze staré školy). V knihovně Underscore najdeme mnoho funkcí pro práci s klasickými JavaScriptovými poli, což sice jsou měnitelné struktury, ovšem samotná knihovna Underscore se k polím chová „funkcionálně“, takže jejich obsah nikdy nemodifikuje (navíc je práce s poli velmi efektivní). Pole se zde používají i ve funkci množin apod. Další části knihovny Underscore si popíšeme až v navazujících kapitolách a taktéž v navazujícím článku, takže se nyní věnujme především polím.
Společné vlastnosti a rozdíly mezi knihovnami Mori a Underscore lze shrnout (s určitým zjednodušením) takto:
Vlastnost | Mori | Underscore |
---|---|---|
Velikost knihovny | 185 kB | 16 kB |
Rychlost (1 thread) | menší | vyšší |
Perzistentní struktury | ano | ne |
Lazy sekvence | ano | ne |
Nekonečné (lazy) sekvence | ano | ne |
Založeno na | struktury ClojureScriptu | Array v JavaScriptu |
Rozšíření std. objektů JS | ne | ne |
Funkce typu range | ano | ano |
Funkce typu map, reduce | ano | ano |
Funkce typu filter | ano | ano |
Vyhledávání v sekvencích | ano | ano |
Predikáty some a every | ano | ano |
Množinové operace | ano | ano |
Memoizace (cache výsledků) | nyní ne | explicitní |
Další vlastnosti | mapy, množiny | funkce pro lepší práci s JS objekty |
3. Základní funkce pro „funkcionální“ práci s poli
V mnoha algoritmech, zejména pak v algoritmech založených na zpracování seznamů (list), zásobníků (stack) a front (queue), se velmi často přistupuje k prvnímu popř. poslednímu prvku nějaké datové sekvence a taktéž se získává původní sekvence bez prvního (popř. posledního) prvku. Pro tyto účely je v knihovně Underscore.js implementována následující čtveřice funkcí (funkce s různým počtem argumentů rozepisuji na více řádků):
# | Funkce | Význam |
---|---|---|
1 | _.first(pole) | vrací první prvek pole |
2 | _.first(pole, n) | vrací prvních n prvků pole |
3 | _.last(pole) | vrací poslední prvek pole |
4 | _.last(pole, n) | vrací posledních n prvků pole |
5 | _.initial(pole) | vrací kopii pole bez posledního prvku |
6 | _.initial(pole, n) | vrací kopii pole bez posledních n prvků |
7 | _.rest(pole) | vrací kopii pole bez prvního prvku |
8 | _.rest(pole) | vrací kopii pole bez prvních n prvků |
Pro některé funkce existují i jmenné aliasy:
# | Funkce | Alias(y) |
---|---|---|
1 | first | head, take |
2 | rest | tail, drop |
4. Funkce uniq
Při tvorbě mnoha aplikací se setkáme s nutností odstranit z pole duplicitní prvky. Pro tyto účely lze použít funkci nazvanou jednoduše uniq. Této funkci se předává pole, výsledkem je nové pole bez duplicitních prvků, přičemž za duplicitní prvky jsou považovány všechny dvojice, kde platí X===Y (=== je JavaScriptový operátor). V případě, že je pole nesetříděno, je použit algoritmus s větší složitostí (ostatně sami se podívejte na jeho implementaci). Pokud je však zapotřebí odstranit duplicitní prvky z již setříděného pole, může knihovna Underscore použít rychlejší algoritmus, což se samozřejmě pozitivně projeví především u delších polí (příkladem může být například klientská strana GIS s mnoha grafickými objekty). V tomto případě je nutné funkci uniq předat ještě druhý argument s pravdivostní hodnotou true, aby byl rychlejší algoritmus vybrán:
# | Funkce | Význam |
---|---|---|
1 | _.uniq(pole) | vrací nové pole s unikátními prvky |
2 | _.uniq(pole, true) | dtto, ovšem rychlejší u setříděného pole |
5. První demonstrační příklad
V dnešním prvním demonstračním příkladu je ukázána práce s výše popsanými funkcemi first, last, initial, rest a taktéž uniq. Příklad je napsán takovým způsobem, aby ho bylo možné spustit přímo ve webovém prohlížeči, například přes tuto jednoduchou webovou stránku:
<!doctype html> <html> <head> <title>Underscore tests #01</title> <meta charset="utf-8"> <script src="underscore-min.js"></script> <script src="underscore_01.js"></script> </head> <body> <h1>Underscore tests #01</h1> </body> </html>
Následuje výpis zdrojového kódu tohoto demonstračního příkladu. Povšimněte si použití eval ve funkci nazvané printArrayInfo(); podobný kód jsme již viděli u popisu knihovny Mori:
// ------------------------------------------------------------ // Knihovna Underscore.js: demonstracni priklad cislo 1 // Zakladni funkce pri praci s poli. // ------------------------------------------------------------ // funkce pro vypis informaci o vybranem poli (ci jinem objektu) function printArrayInfo(expression) { var anArray = eval(expression); console.log("---------------------------------"); console.log(expression); // zjisteni typu objektu (a pripadne delky pole) if (anArray instanceof Array) { console.log("type: array"); console.log("length: " + anArray.length); } // jiny typ objektu, nemame zde jistotu, ze existuje atribut length else { console.log("type: " + typeof anArray); } console.log("content: " + anArray); } // vytvoreni bezneho pole, nad kterym se budou provadet nektere operace var array1 = [1,2,3,4,5]; // informace o samotnem vstupnim poli printArrayInfo(array1); // zakladni operace nad polem // funkce _.first() printArrayInfo("_.first(array1)"); printArrayInfo("_.first(array1, 2)"); printArrayInfo("_.first(array1, 100)"); // n je vetsi nez delka pole // funkce _.last() printArrayInfo("_.last(array1)"); printArrayInfo("_.last(array1, 2)"); printArrayInfo("_.last(array1, 100)"); // n je vetsi nez delka pole // funkce _.initial() printArrayInfo("_.initial(array1)"); printArrayInfo("_.initial(array1, 2)"); printArrayInfo("_.initial(array1, 100)"); // n je vetsi nez delka pole // funkce _.rest() printArrayInfo("_.rest(array1)"); printArrayInfo("_.rest(array1, 2)"); printArrayInfo("_.rest(array1, 100)"); // n je vetsi nez delka pole // otestovani funkce uniq var array2 = [1,2,3,3,2,1]; var array3 = [3,2,1,1,2,3]; var array4 = [1,2,2,3,3,3,4,4,4,4]; // informace o samotnem vstupnim poli printArrayInfo(array2); printArrayInfo(array3); printArrayInfo(array4); // vytvoreni pole s unikatnimi hodnotami printArrayInfo("_.uniq(array2)"); printArrayInfo("_.uniq(array3)"); // toto pole je setrideno, muzeme pouzit rychlejsi algoritmus printArrayInfo("_.uniq(array4, true)"); // plati o pro pole retezcu atd. var names = ["Perl", "Python", "Java", "JavaScript", "C", "Lua", "Clojure", "Lua", "C"]; printArrayInfo("names"); printArrayInfo("_.uniq(names)"); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Výsledek běhu prvního demonstračního příkladu může vypadat následovně (konzoli je možné zobrazit například přes Firebug apod.):
--------------------------------- [1, 2, 3, 4, 5] type: array length: 5 content: 1,2,3,4,5 --------------------------------- _.first(array1) type: number content: 1 --------------------------------- _.first(array1, 2) type: array length: 2 content: 1,2 --------------------------------- _.first(array1, 100) type: array length: 5 content: 1,2,3,4,5 --------------------------------- _.last(array1) type: number content: 5 --------------------------------- _.last(array1, 2) type: array length: 2 content: 4,5 --------------------------------- _.last(array1, 100) type: array length: 5 content: 1,2,3,4,5 --------------------------------- _.initial(array1) type: array length: 4 content: 1,2,3,4 --------------------------------- _.initial(array1, 2) type: array length: 3 content: 1,2,3 --------------------------------- _.initial(array1, 100) type: array length: 0 content: --------------------------------- _.rest(array1) type: array length: 4 content: 2,3,4,5 --------------------------------- _.rest(array1, 2) type: array length: 3 content: 3,4,5 --------------------------------- _.rest(array1, 100) type: array length: 0 content: --------------------------------- [1, 2, 3, 3, 2, 1] type: array length: 6 content: 1,2,3,3,2,1 --------------------------------- [3, 2, 1, 1, 2, 3] type: array length: 6 content: 3,2,1,1,2,3 --------------------------------- [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] type: array length: 10 content: 1,2,2,3,3,3,4,4,4,4 --------------------------------- _.uniq(array2) type: array length: 3 content: 1,2,3 --------------------------------- _.uniq(array3) type: array length: 3 content: 3,2,1 --------------------------------- _.uniq(array4, true) type: array length: 4 content: 1,2,3,4 --------------------------------- names type: array length: 9 content: Perl,Python,Java,JavaScript,C,Lua,Clojure,Lua,C --------------------------------- _.uniq(names) type: array length: 7 content: Perl,Python,Java,JavaScript,C,Lua,Clojure
6. Různé varianty funkce range
V knihovně Underscore nalezneme i velmi užitečnou funkci nazvanou range, která zde slouží pro vytvoření pole obsahujícího sekvenci číselných hodnot (výsledkem je tedy skutečné pole, nikoli iterátor). Užitečnost této funkce se ve skutečnosti ještě řádově zvýší ve chvíli, kdy se (už příště) seznámíme s funkcemi map, reduce, filter, each a zip, které knihovna Underscore samozřejmě taktéž nabízí. Funkci range je možné předat počáteční hodnotu, mezní hodnotu a taktéž krok (rozdíl hodnoty mezi dvěma prvky pole), ovšem dva argumenty, konkrétně počáteční hodnota a krok jsou ve skutečnosti nepovinné.
Zajímavé je, že počáteční hodnota, mezní hodnota i krok mohou být reálná čísla, takže se tato funkce svým chováním odlišuje například od stejně pojmenované funkce range známé z Pythonu 2.x (a samozřejmě ještě mnohem více od stejnojmenné funkce v Pythonu 3.x, která má specifické chování). V následující tabulce jsou ukázány některé možnosti volání funkce range z knihovny Underscore:
# | Funkce | Význam |
---|---|---|
1 | _.range(stop) | vrací nové pole s prvky 0, 1, … stop-1 |
2 | _.range(start, stop) | vrací nové pole s prvky start, start+1, … stop-1 |
3 | _.range(start, stop, step) | vrací nové pole s prvky start, start+step, start+2*step, až po mez (ta není překročena) |
Poznámky:
- Všechny tři argumenty jsou typu Number (základní datový typ JavaScriptu), tj. jedná se o reálná čísla typicky typu double. Neexistuje zde omezení na použití celých čísel (na rozdíl například od Pythonu).
- Po zavolání range(0) se podle očekávání vytvoří prázdné pole.
- Pokud platí podmínka start>stop a krok je kladný, opět se vytvoří prázdné pole.
- Pokud je start>stop a krok je záporný, vytvoří se běžná sekvence.
- Vzhledem k tomu, že se hlídá překročení zadané meze a nikoli její přesné dosažení, lze pro zadání kroku použít i v jiných případech problematické hodnoty typu 0,1 (hodnoty vytvořených prvků sice logicky nebudou přesné, ovšem alespoň nedojde k nekonečné smyčce).
7. Druhý demonstrační příklad
Většina možných a podporovaných variant výše popsané funkce range je vysvětlena ve druhém demonstračním příkladu, jehož úplný zdrojový kód je vypsán pod tímto odstavcem:
// ------------------------------------------------------------ // Knihovna Underscore.js: demonstracni priklad cislo 2 // Pouziti funkce range(). // ------------------------------------------------------------ // funkce pro vypis informaci o vybranem poli (ci jinem objektu) function printArrayInfo(expression) { var anArray = eval(expression); console.log("---------------------------------"); console.log(expression); // zjisteni typu objektu (a pripadne delky pole) if (anArray instanceof Array) { console.log("type: array"); console.log("length: " + anArray.length); } // jiny typ objektu, nemame zde jistotu, ze existuje atribut length else { console.log("type: " + typeof anArray); } console.log("content: " + anArray); } // informace o polich vytvorenych funkci range printArrayInfo("_.range(10)"); printArrayInfo("_.range(-10)"); printArrayInfo("_.range(0, 10)"); printArrayInfo("_.range(10, 0)"); printArrayInfo("_.range(10, 0, -1)"); printArrayInfo("_.range(0, 10, 2)"); printArrayInfo("_.range(0, 10, -2)"); printArrayInfo("_.range(10, 0, 2)"); printArrayInfo("_.range(10, 0, -2)"); printArrayInfo("_.range(0, 10, 100)"); // lze pouzit i desetinna cisla printArrayInfo("_.range(10, 15, 0.5)"); printArrayInfo("_.range(10, 5,-0.5)"); // desetinna cisla pro urceni zacatku i konce printArrayInfo("_.range(1.5, 3.5, 1/2)"); printArrayInfo("_.range(3.2, 1.8, -1/4)"); printArrayInfo("_.range(1/2, 3/4, 1/10)"); // stara znama chyba se zde projevuje jen castecne printArrayInfo("_.range(0, 10, 0.1)"); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Poznámka: 1/2, –1/4 atd. jsou v JavaScriptu výrazy, které je zde možné bez problémů použít.
Výsledek běhu druhého demonstračního příkladu může vypadat následovně (konzoli je opět možné zobrazit například přes Firebug apod.):
--------------------------------- _.range(10) type: array length: 10 content: 0,1,2,3,4,5,6,7,8,9 --------------------------------- _.range(-10) type: array length: 0 content: --------------------------------- _.range(0, 10) type: array length: 10 content: 0,1,2,3,4,5,6,7,8,9 --------------------------------- _.range(10, 0) type: array length: 0 content: --------------------------------- _.range(10, 0, -1) type: array length: 10 content: 10,9,8,7,6,5,4,3,2,1 --------------------------------- _.range(0, 10, 2) type: array length: 5 content: 0,2,4,6,8 --------------------------------- _.range(0, 10, -2) type: array length: 0 content: --------------------------------- _.range(10, 0, 2) type: array length: 0 content: --------------------------------- _.range(10, 0, -2) type: array length: 5 content: 10,8,6,4,2 --------------------------------- _.range(0, 10, 100) type: array length: 1 content: 0 --------------------------------- _.range(10, 15, 0.5) type: array length: 10 content: 10,10.5,11,11.5,12,12.5,13,13.5,14,14.5 --------------------------------- _.range(10, 5,-0.5) type: array length: 10 content: 10,9.5,9,8.5,8,7.5,7,6.5,6,5.5 --------------------------------- _.range(1.5, 3.5, 1/2) type: array length: 4 content: 1.5,2,2.5,3 --------------------------------- _.range(3.2, 1.8, -1/4) type: array length: 6 content: 3.2,2.95,2.7,2.45,2.2,1.9500000000000002 --------------------------------- _.range(1/2, 3/4, 1/10) type: array length: 3 content: 0.5,0.6,0.7 --------------------------------- _.range(0, 10, 0.1) type: array length: 100 content: 0,0.1,0.2,0.30000000000000004,0.4,0.5,0.6,0.7,0.7999999999999999,0.8999999999999999, 0.9999999999999999,1.0999999999999999,1.2,1.3,1.4000000000000001,1.5000000000000002, 1.6000000000000003,1.7000000000000004,1.8000000000000005,1.9000000000000006,2.0000000000000004, 2.1000000000000005,2.2000000000000006,2.3000000000000007,2.400000000000001,2.500000000000001, 2.600000000000001,2.700000000000001,2.800000000000001,2.9000000000000012,3.0000000000000013, 3.1000000000000014,3.2000000000000015,3.3000000000000016,3.4000000000000017,3.5000000000000018, 3.600000000000002,3.700000000000002,3.800000000000002,3.900000000000002,4.000000000000002, 4.100000000000001,4.200000000000001,4.300000000000001,4.4,4.5,4.6,4.699999999999999, 4.799999999999999,4.899999999999999,4.999999999999998,5.099999999999998,5.1999999999999975, 5.299999999999997,5.399999999999997,5.4999999999999964,5.599999999999996,5.699999999999996, 5.799999999999995,5.899999999999995,5.999999999999995,6.099999999999994,6.199999999999994, 6.299999999999994,6.399999999999993,6.499999999999993,6.5999999999999925,6.699999999999992, 6.799999999999992,6.8999999999999915,6.999999999999991,7.099999999999991,7.19999999999999, 7.29999999999999,7.39999999999999,7.499999999999989,7.599999999999989,7.699999999999989, 7.799999999999988,7.899999999999988,7.999999999999988,8.099999999999987,8.199999999999987, 8.299999999999986,8.399999999999986,8.499999999999986,8.599999999999985,8.699999999999985, 8.799999999999985,8.899999999999984,8.999999999999984,9.099999999999984,9.199999999999983, 9.299999999999983,9.399999999999983,9.499999999999982,9.599999999999982,9.699999999999982, 9.799999999999981,9.89999999999998
8. Vyhledávání prvků v polích
Na první pohled jednoduché, ale o to užitečnější mohou být v praxi funkce určené pro vyhledávání prvků v polích. Knihovna Underscore programátorům nabízí celkem čtyři tyto funkce, které se od sebe odlišují tím, zda vyhledávají prvky od začátku pole, od konce pole a taktéž tím, zda se prvek vyhledává přímo podle své hodnoty či na základě nějakého predikátu, tj. funkce postupně volané pro jednotlivé prvky. V každém případě platí, že po nalezení prvního vhodného prvku se vyhledání zastaví a daná funkce vrátí index tohoto prvku. Pokud prvek není nalezen, vrátí se hodnota –1, tj. neexistující index (prvky v běžných polích jsou v JavaScriptu indexovány od nuly). Podívejme se nyní na všechny čtyři vyhledávací funkce:
# | Funkce | Význam |
---|---|---|
1 | _.indexOf(pole, hodnota) | vyhledávání prvního prvku s danou hodnotou od začátku pole |
2 | _.indexOf(pole, hodnota, index) | vyhledávání prvního prvku s danou hodnotou od zadaného indexu směrem ke konci pole |
3 | _.indexOf(pole, hodnota, true) | binární vyhledávání v setříděném poli |
4 | _.lastIndexOf(pole, hodnota) | vyhledávání posledního prvku s danou hodnotou |
5 | _.lastIndexOf(pole, hodnota, index) | vyhledávání posledního prvku od zadaného indexu |
6 | _.findIndex(pole, predikát) | nalezení prvního prvku, pro nějž predikát vrátil hodnotu true |
7 | _.findLastIndex(pole, predikát) | nalezení posledního prvku, pro nějž predikát vrátil hodnotu true |
Poznámka: použití funkce findIndex s vhodným predikátem dokáže z vytvářených programů odstranit poměrně velké množství programových smyček.
9. Třetí demonstrační příklad
Všechny čtyři funkce popsané v předchozí kapitole jsou použity v dnešním třetím demonstračním příkladu, jehož zdrojový kód je vypsán pod tímto odstavcem. Povšimněte si především použití vlastních predikátů (funkcí vracejících pro každý prvek true či false) u funkcí findIndex a findLastIndex. Dva z těchto predikátů vrací konstantu bez ohledu na to, jaký prvek je jim předán:
// ------------------------------------------------------------ // Knihovna Underscore.js: demonstracni priklad cislo 3 // Pouziti funkci pro vyhledani prvku v polich. // ------------------------------------------------------------ var names = ["C", "C++", "C#", "Java", "Perl", "Python", "Ruby", "JavaScript", "CoffeeScript", "Dart", "Wisp", // prvky se opakuji "C", "C++", "C#", "Java", "Lua", "Clojure", "Lisp", "Forth"]; function printArrayItem(anArray, index) { console.log("#" + i + " = " + anArray[i]); } var i; console.log("_.indexOf():"); // bezne vyhledavani prvniho prvku i = _.indexOf(names, "Java"); printArrayItem(names, i); // vyhledavani od desateho prvku i = _.indexOf(names, "Java", 10); printArrayItem(names, i); // bezne vyhledavani neexistujiciho prvku i = _.indexOf(names, "BASIC"); printArrayItem(names, i); console.log("---------------------------------------"); console.log("_.lastIndexOf():"); // bezne vyhledavani posledniho prvku i = _.lastIndexOf(names, "Java"); printArrayItem(names, i); // vyhledavani od desateho prvku i = _.lastIndexOf(names, "Java", 10); printArrayItem(names, i); // bezne vyhledavani neexistujiciho prvku i = _.lastIndexOf(names, "BASIC"); printArrayItem(names, i); console.log("---------------------------------------"); /* predikat vracejici true pouze v pripade, ze se v prvku * nalezne podretezec odpovidajici regularnimu vyrazu.*/ function predicate(item) { return /.*Java.*/.test(item); } /* predikat vracejici vzdy hodnotu true */ function constantTrue(item) { return true; } /* predikat vracejici vzdy hodnotu false */ function constantFalse(item) { return false; } console.log("_.findIndex():"); i = _.findIndex(names, predicate); printArrayItem(names, i); i = _.findIndex(names, constantTrue); printArrayItem(names, i); i = _.findIndex(names, constantFalse); printArrayItem(names, i); console.log("---------------------------------------"); console.log("_.findLastIndex():"); i = _.findLastIndex(names, predicate); printArrayItem(names, i); i = _.findLastIndex(names, constantTrue); printArrayItem(names, i); i = _.findLastIndex(names, constantFalse); printArrayItem(names, i); console.log("---------------------------------------"); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Výsledek běhu třetího demonstračního příkladu může vypadat následovně (konzoli je opět možné zobrazit například přes Firebug apod.):
_.indexOf(): #3 = Java #14 = Java #-1 = undefined --------------------------------------- _.lastIndexOf(): #14 = Java #3 = Java #-1 = undefined --------------------------------------- _.findIndex(): #3 = Java #0 = C #-1 = undefined --------------------------------------- _.findLastIndex(): #14 = Java #18 = Forth #-1 = undefined ---------------------------------------
10. Množinové operace s poli
V mnoha programech, v nichž se volají funkce nabízené knihovnou Underscore, se pole používají i jako náhrada za množiny, ostatně většina databázových operací ve skutečnosti vrací množiny či setříděné množiny a nikoli seznamy. Programátoři mají k dispozici trojici funkcí nazvaných union, intersection a difference, s jejichž využitím se implementují operace pro sjednocení množin, průnik množin a rozdíl množin. Nejprve se podívejme na to, jak tyto funkce vypadají a jaké očekávají parametry:
# | Funkce | Význam |
---|---|---|
1 | _.union(pole1, …) | provedení operace sjednocení množin |
2 | _.intersection(pole1, …) | provedení operace průniku množin |
3 | _.difference(pole1, pole2, …) | provedení operace rozdílu množin |
Poznámky:
- Funkce union a intersection akceptují alespoň jeden parametr typu pole. Pokud je jim předán jediný parametr, logicky se vrátí původní pole. Pokud je těmto funkcím předáno větší množství parametrů (teoreticky libovolný počet), provede se se všemi poli daná operace a vrátí se nové pole.
- Naproti tomu funkce difference akceptuje minimálně dva parametry. Taktéž zde záleží na pořadí polí, protože výsledkem této funkce je pole s prvky, které se nachází v prvním poli a současně se nenachází v žádných dalších polích.
Podívejme se na příklady, z nichž bude chování všech tří funkcí patrnější:
_.union([1,2,3], [2,3,4]) [1,2,3,4] _.union([1,2,3], [4,5,6]) [1,2,3,4,5,6] _.intersection([1,2,3], [2,3,4]) [2,3] _.intersection([1,2,3], [4,5,6]) [] _.difference([1,2,3], [2,3,4]) [1] _.difference([1,2,3], [4,5,6]) [1,2,3] _.difference([4,5,6], [1,2,3]) [4,5,6])
11. Čtvrtý demonstrační příklad
Množinové operace zmíněné v předchozí kapitole si můžeme vyzkoušet ve čtvrtém (předposledním) demonstračním příkladu. Podívejme se nyní na zdrojový kód tohoto příkladu, který je vlastně ve skutečnosti velmi jednoduchý:
// ------------------------------------------------------------ // Knihovna Underscore.js: demonstracni priklad cislo 4 // Mnozinove operace s poli. // ------------------------------------------------------------ // funkce pro vypis informaci o vybranem poli (ci jinem objektu) function printArrayInfo(expression) { var anArray = eval(expression); console.log("---------------------------------"); console.log(expression); // zjisteni typu objektu (a pripadne delky pole) if (anArray instanceof Array) { console.log("type: array"); console.log("length: " + anArray.length); } // jiny typ objektu, nemame zde jistotu, ze existuje atribut length else { console.log("type: " + typeof anArray); } console.log("content: " + anArray); } // pole s prvky 1..10 var array1 = _.range(1, 11); // pole s prvky 6..15 var array2 = _.range(6, 16); // pole s prvky 101..110 var array3 = _.range(101, 111); printArrayInfo("array1"); printArrayInfo("array2"); printArrayInfo("array3"); // sjednoceni mnozin printArrayInfo("_.union(array1, array2)"); printArrayInfo("_.union(array2, array1)"); printArrayInfo("_.union(array1, array3)"); printArrayInfo("_.union(array1, array2, array3)"); // prunik mnozin se spolecnymi prvky printArrayInfo("_.intersection(array1, array2)"); printArrayInfo("_.intersection(array2, array1)"); // mnoziny nemaji spolecne prvky printArrayInfo("_.intersection(array1, array3)"); // mnoziny nemaji spolecne prvky printArrayInfo("_.intersection(array1, array2, array3)"); // rozdil mnozin (zde samozrejme zalezi na poradi) printArrayInfo("_.difference(array1, array2)"); printArrayInfo("_.difference(array2, array1)"); printArrayInfo("_.difference(array1, array3)"); printArrayInfo("_.difference(array3, array1)"); printArrayInfo("_.difference(array1, array2, array3)"); // specialni pripad :) printArrayInfo("_.difference(array1, array1)"); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Výsledek běhu čtvrtého demonstračního příkladu může vypadat následovně (konzoli je … ale to už víte):
--------------------------------- array1 type: array length: 10 content: 1,2,3,4,5,6,7,8,9,10 --------------------------------- array2 type: array length: 10 content: 6,7,8,9,10,11,12,13,14,15 --------------------------------- array3 type: array length: 10 content: 101,102,103,104,105,106,107,108,109,110 --------------------------------- _.union(array1, array2) type: array length: 15 content: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --------------------------------- _.union(array2, array1) type: array length: 15 content: 6,7,8,9,10,11,12,13,14,15,1,2,3,4,5 --------------------------------- _.union(array1, array3) type: array length: 20 content: 1,2,3,4,5,6,7,8,9,10,101,102,103,104,105,106,107,108,109,110 --------------------------------- _.union(array1, array2, array3) type: array length: 25 content: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,101,102,103,104,105,106,107,108,109,110 --------------------------------- _.intersection(array1, array2) type: array length: 5 content: 6,7,8,9,10 --------------------------------- _.intersection(array2, array1) type: array length: 5 content: 6,7,8,9,10 --------------------------------- _.intersection(array1, array3) type: array length: 0 content: --------------------------------- _.intersection(array1, array2, array3) type: array length: 0 content: --------------------------------- _.difference(array1, array2) type: array length: 5 content: 1,2,3,4,5 --------------------------------- _.difference(array2, array1) type: array length: 5 content: 11,12,13,14,15 --------------------------------- _.difference(array1, array3) type: array length: 10 content: 1,2,3,4,5,6,7,8,9,10 --------------------------------- _.difference(array3, array1) type: array length: 10 content: 101,102,103,104,105,106,107,108,109,110 --------------------------------- _.difference(array1, array2, array3) type: array length: 5 content: 1,2,3,4,5 --------------------------------- _.difference(array1, array1) type: array length: 0 content:
12. Zploštění vnořených polí, kombinace prvků z různých polí atd.
V knihovně Underscore nalezneme i mnohé další užitečné funkce používané pro práci s poli, kombinaci prvků z různých polí apod. Jedná se především o funkce nazvané flatten, zip a unzip (pozor na to, že zejména funkce zip má v jiných jazycích či knihovnách někdy poněkud odlišný význam, což se týká například i ClojureScriptu a tudíž i dříve popsané knihovny Wisp). Způsob volání těchto funkcí je vypsán v následující tabulce, pod tabulkou pak následují podrobnější popisy jednotlivých funkcí:
# | Funkce | Význam |
---|---|---|
1 | _.flatten(pole) | zploštění pole (resp. rekurzivně všech pod-polí) |
2 | _.flatten(pole, true) | zploštění pole, ovšem jen v první úrovni |
3 | _.zip(sekvence polí) | ze vstupních polí vytvoří nové pole, viz popis a příklady uvedené níže |
4 | _.unzip(pole) | opak předchozí funkce, ze vstupního pole vytvoří sekvenci polí |
5 | _.object(pole) | (zde uvedeno pouze pro doplnění, bude vysvětleno příště), toto je obdoba funkce zip z ClojureScriptu a knihovny Wisp, protože převádí pole na objekty sloužící i jako mapa) |
Funkce flatten pracuje tak, jak její název napovídá: „zplošťuje“ pole, což znamená, že se ve vstupním poli (které zůstává nezměněno) všechny prvky obsahující (i rekurzivně) taktéž pole rozloží na jednotlivé elementy. Druhým argumentem (pokud se mu předá hodnota true) lze zploštění omezit pouze na první úroveň, tj. zploštění se nebude provádět rekurzivně. Podívejme se na příklady, z nichž bude chování této funkce zřejmé:
_.flatten([1, 2, 3, 4, 5]); [1, 2, 3, 4, 5] _.flatten([[1, 2], [3, 4], 5]); [1, 2, 3, 4, 5] _.flatten([1, [2, [3, [4, [5]]]]]); [1, 2, 3, 4, 5] _.flatten([1, [2], [3, [[4]]]], true); [1, 2, 3, [[4]]];
Poněkud složitěji se chová funkce zip, které je možné předat libovolný počet polí o různé délce. Tato funkce vytvoří nové pole obsahující jako své prvky n-tice (což zde není nic jiného než pole). První prvek bude n-ticí (polem) složeným z prvních prvků všech předaných polí, druhý prvek bude n-tice složená ze druhých prvků předaných polí atd. Vstupní pole mohou mít rozdílnou délku, takže jednotlivé n-tice nemusí mít stejný počet prvků. Opět se podívejme na příklady:
// pole s prvky 1..10 var array1 = _.range(1, 11); // pole s prvky "A".."J" var array2 = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]; // vytvoření pole dvojic _.zip(array1, array2) [[1,A], [2,B], [3,C], [4,D], [5,E], [6,F], [7,G], [8,H], [9,I], [10,J]] // vytvoření pole dvojic _.zip(array2, array2) [[A,A], [B,B], [C,C], [D,D], [E,E], [F,F], [G,G], [H,H], [I,I], [J,J]] // vytvoření pole trojic dvojic a posléze dvojic _.zip(array1, array2, [100,200,300]) [[1,A,100], [2,B,200], [3,C,300], [4,D], [5,E], [6,F], [7,G], [8,H], [9,I], [10,J]]
Poznámka: již několikrát jsme se setkali s tím, že JavaScript a tím pádem i knihovna Underscore umožňuje práci s nepravidelnými 2D maticemi, tj. maticemi, jejichž řádky mají nestejnou délku. To je i případ funkce zip, která v obecném případě právě takové nepravidelné 2D matice vytváří.
13. Pátý demonstrační příklad
Chování funkcí flatten a zip, které jsme si ve stručnosti popsali v předchozí kapitole, si otestujeme v dnešním pátém a současně i posledním demonstračním příkladu jehož zdrojový kód je vypsán pod tímto odstavcem. Povšimněte si, že zde mj. používáme i funkci range:
// ------------------------------------------------------------ // Knihovna Underscore.js: demonstracni priklad cislo 5 // Operace zip a flatten. // ------------------------------------------------------------ // funkce pro vypis informaci o vybranem poli (ci jinem objektu) function printArrayInfo(expression) { var anArray = eval(expression); console.log("---------------------------------"); console.log(expression); // zjisteni typu objektu (a pripadne delky pole) if (anArray instanceof Array) { console.log("type: array"); console.log("length: " + anArray.length); // nyni pracujeme s 2D poli, takze si postupne vypiseme jeho radky // (console.log totiz vypise i 2D pole jako jeden vektor) for (var i=0; i < anArray.length; i++) { console.log("row #" + i + " = " + anArray[i]); } } // jiny typ objektu, nemame zde jistotu, ze existuje atribut length else { console.log("type: " + typeof anArray); console.log("content: " + anArray); } } // pole s prvky 1..10 var array1 = _.range(1, 11); // pole s prvky "A".."J" var array2 = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]; printArrayInfo("array1"); printArrayInfo("array2"); // kombinace prvku ze dvou poli printArrayInfo("_.zip(array1, array2)"); printArrayInfo("_.zip(array2, array1)"); // kombinace prvku ze stejneho vstupniho pole printArrayInfo("_.zip(array2, array2)"); // i toto je povoleno, posledni pole je kratsi printArrayInfo("_.zip(array1, array2, [100,200,300])"); // otestovani funkce flatten() var array3 = [_.range(1, 6), _.range(5,10), _.range(9,15)]; printArrayInfo("array3"); printArrayInfo("_.flatten(array3)"); // ------------------------------------------------------------ // Finito // ------------------------------------------------------------
Výsledek běhu pátého demonstračního příkladu může vypadat následovně (konzoli je … ale to už víte):
--------------------------------- array1 type: array length: 10 row #0 = 1 row #1 = 2 row #2 = 3 row #3 = 4 row #4 = 5 row #5 = 6 row #6 = 7 row #7 = 8 row #8 = 9 row #9 = 10 --------------------------------- array2 type: array length: 10 row #0 = A row #1 = B row #2 = C row #3 = D row #4 = E row #5 = F row #6 = G row #7 = H row #8 = I row #9 = J --------------------------------- _.zip(array1, array2) type: array length: 10 row #0 = 1,A row #1 = 2,B row #2 = 3,C row #3 = 4,D row #4 = 5,E row #5 = 6,F row #6 = 7,G row #7 = 8,H row #8 = 9,I row #9 = 10,J --------------------------------- _.zip(array2, array1) type: array length: 10 row #0 = A,1 row #1 = B,2 row #2 = C,3 row #3 = D,4 row #4 = E,5 row #5 = F,6 row #6 = G,7 row #7 = H,8 row #8 = I,9 row #9 = J,10 --------------------------------- _.zip(array2, array2) type: array length: 10 row #0 = A,A row #1 = B,B row #2 = C,C row #3 = D,D row #4 = E,E row #5 = F,F row #6 = G,G row #7 = H,H row #8 = I,I row #9 = J,J --------------------------------- _.zip(array1, array2, [100,200,300]) type: array length: 10 row #0 = 1,A,100 row #1 = 2,B,200 row #2 = 3,C,300 row #3 = 4,D, row #4 = 5,E, row #5 = 6,F, row #6 = 7,G, row #7 = 8,H, row #8 = 9,I, row #9 = 10,J, --------------------------------- array3 type: array length: 3 row #0 = 1,2,3,4,5 row #1 = 5,6,7,8,9 row #2 = 9,10,11,12,13,14 --------------------------------- _.flatten(array3) type: array length: 16 row #0 = 1 row #1 = 2 row #2 = 3 row #3 = 4 row #4 = 5 row #5 = 5 row #6 = 6 row #7 = 7 row #8 = 8 row #9 = 9 row #10 = 9 row #11 = 10 row #12 = 11 row #13 = 12 row #14 = 13 row #15 = 14
14. Repositář s demonstračními příklady
Všech pět demonstračních příkladů, které jsme si v dnešním článku popsali, bylo uloženo do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy všech pěti demonstračních příkladů přímé odkazy (navíc je doplněn i odkaz na aktuálně použitou variantu knihovny Underscore):
# | Příklad/knihovna | Github |
---|---|---|
1 | underscore01.js | https://github.com/tisnik/presentations/blob/master/underscore/underscore01.js |
2 | underscore02.js | https://github.com/tisnik/presentations/blob/master/underscore/underscore02.js |
3 | underscore03.js | https://github.com/tisnik/presentations/blob/master/underscore/underscore03.js |
4 | underscore04.js | https://github.com/tisnik/presentations/blob/master/underscore/underscore04.js |
5 | underscore05.js | https://github.com/tisnik/presentations/blob/master/underscore/underscore05.js |
6 | underscore-min.js | https://github.com/tisnik/presentations/blob/master/underscore/underscore-min.js |
15. Odkazy na Internetu
- Stránky knihovny Underscore s popisem všech funkcí
http://underscorejs.org/ - Minifikovaná verze knihovny Underscore
http://underscorejs.org/underscore-min.js - Functional Programming (Wikipedia)
https://en.wikipedia.org/wiki/Functional_programming - An Introduction to Functional Programming in JavaScript
http://www.srirangan.net/2011–12-functional-programming-in-javascript - Getting Cozy With Underscore.js
http://code.tutsplus.com/tutorials/getting-cozy-with-underscorejs–net-24581 - Mori na GitHubu
https://github.com/swannodette/mori - Mori: popis API (dokumentace)
http://swannodette.github.io/mori/ - Mori: Benchmarking
https://github.com/swannodette/mori/wiki/Benchmarking - Functional data structures in JavaScript with Mori
http://sitr.us/2013/11/04/functional-data-structures.html - Immutable.js
https://facebook.github.io/immutable-js/ - Persistent data structure
https://en.wikipedia.org/wiki/Persistent_data_structure - Understanding Clojure's Persistent Vectors, pt. 1
http://hypirion.com/musings/understanding-persistent-vector-pt-1 - Hash array mapped trie (Wikipedia)
https://en.wikipedia.org/wiki/Hash_array_mapped_trie - Java theory and practice: To mutate or not to mutate?
http://www.ibm.com/developerworks/java/library/j-jtp02183/index.html - Efficient persistent (immutable) data structures
https://persistent.codeplex.com/ - Netscape Enterprise Server (Wikipedia)
https://en.wikipedia.org/wiki/Netscape_Enterprise_Server - SSJS Reference Guide (Server-Side JavaScript)
http://docs.oracle.com/cd/E19957–01/816–6410–10/816–6410–10.pdf - Atom: moderní textový editor
http://www.root.cz/clanky/atom-moderni-textovy-editor/ - Atom: moderní textový editor (dokončení)
http://www.root.cz/clanky/atom-moderni-textovy-editor-dokonceni/ - Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp
http://www.root.cz/clanky/propojeni-sveta-lispu-se-svetem-javascriptu-s-vyuzitim-transprekladace-wisp/ - Propojení světa LISPu se světem JavaScriptu s využitím transpřekladače Wisp (2.část)
http://www.root.cz/clanky/propojeni-sveta-lispu-se-svetem-javascriptu-s-vyuzitim-transprekladace-wisp-2-cast/ - Wisp na GitHubu
https://github.com/Gozala/wisp - Wisp playground
http://www.jeditoolkit.com/try-wisp/ - REPL v prohlížeči
http://www.jeditoolkit.com/interactivate-wisp/ - Minification (programming)
https://en.wikipedia.org/wiki/Minification_(programming) - Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html - Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html - Clojure Macro Tutorial (Part III: Syntax Quote)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html - Tech behind Tech: Clojure Macros Simplified
http://techbehindtech.com/2010/09/28/clojure-macros-simplified/ - Fatvat – Exploring functional programming: Clojure Macros
http://www.fatvat.co.uk/2009/02/clojure-macros.html - Eulerovo číslo
http://cs.wikipedia.org/wiki/Eulerovo_??slo - List comprehension
http://en.wikipedia.org/wiki/List_comprehension - List Comprehensions in Clojure
http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html - Clojure Programming Concepts: List Comprehension
http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#List_Comprehension - Clojure core API: for macro
http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/for - cirrus machina – The Clojure for macro
http://www.cirrusmachina.com/blog/comment/the-clojure-for-macro/ - Clojure.org: Clojure home page
http://clojure.org/downloads - Clojure.org: Vars and the Global Environment
http://clojure.org/Vars - Clojure.org: Refs and Transactions
http://clojure.org/Refs - Clojure.org: Atoms
http://clojure.org/Atoms - Clojure.org: Agents as Asynchronous Actions
http://clojure.org/agents - A Couple of Clojure Agent Examples
http://lethain.com/a-couple-of-clojure-agent-examples/ - Clojure – Functional Programming for the JVM
http://java.ociweb.com/mark/clojure/article.html - Clojure quick reference
http://faustus.webatu.com/clj-quick-ref.html - 4Clojure
http://www.4clojure.com/ - ClojureDoc
http://clojuredocs.org/ - Clojure (Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - Riastradh's Lisp Style Rules
http://mumble.net/~campbell/scheme/style.txt - Dynamic Languages Strike Back
http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html - Scripting: Higher Level Programming for the 21st Century
http://www.tcl.tk/doc/scripting.html - Java Virtual Machine Support for Non-Java Languages
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html - The Lua VM, on the Web
https://kripken.github.io/lua.vm.js/lua.vm.js.html - Lua.vm.js REPL
https://kripken.github.io/lua.vm.js/repl.html - lua2js
https://www.npmjs.com/package/lua2js - lua2js na GitHubu
https://github.com/basicer/lua2js-dist - Seriál o programovacím jazyku Lua
http://www.root.cz/serialy/programovaci-jazyk-lua/ - Source-to-source compiler
https://en.wikipedia.org/wiki/Source-to-source_compiler - JavaScript is Assembly Language for the Web: Sematic Markup is Dead! Clean vs. Machine-coded HTML
http://www.hanselman.com/blog/JavaScriptIsAssemblyLanguageForTheWebSematicMarkupIsDeadCleanVsMachinecodedHTML.aspx - JavaScript is Web Assembly Language and that's OK.
http://www.hanselman.com/blog/JavaScriptIsWebAssemblyLanguageAndThatsOK.aspx - Dart
https://www.dartlang.org/ - CoffeeScript
http://coffeescript.org/ - TypeScript
http://www.typescriptlang.org/ - Lua (programming language)
http://en.wikipedia.org/wiki/Lua_(programming_language) - Static single assignment form (SSA)
http://en.wikipedia.org/wiki/Static_single_assignment_form - LuaJIT 2.0 SSA IRhttp://wiki.luajit.org/SSA-IR-2.0
- The LuaJIT Project
http://luajit.org/index.html - LuaJIT FAQ
http://luajit.org/faq.html - LuaJIT Performance Comparison
http://luajit.org/performance.html - LuaJIT 2.0 intellectual property disclosure and research opportunities
http://article.gmane.org/gmane.comp.lang.lua.general/58908 - LuaJIT Wiki
http://wiki.luajit.org/Home - LuaJIT 2.0 Bytecode Instructions
http://wiki.luajit.org/Bytecode-2.0 - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Lua 5.2 sources
http://www.lua.org/source/5.2/ - Tcl Plugin Version 3
http://www.tcl.tk/software/plugin/ - JavaScript: The Web Assembly Language?
http://www.informit.com/articles/article.aspx?p=1856657 - asm.js
http://asmjs.org/ - List of languages that compile to JS
https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS - REPL
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - The LLVM Compiler Infrastructure
http://llvm.org/ProjectsWithLLVM/ - clang: a C language family frontend for LLVM
http://clang.llvm.org/ - emscripten
http://kripken.github.io/emscripten-site/ - LLVM Backend („Fastcomp“)
http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend - Emscripten – Fastcomp na GitHubu
https://github.com/kripken/emscripten-fastcomp - Clang (pro Emscripten) na GitHubu
https://github.com/kripken/emscripten-fastcomp-clang - Why not use JavaScript?
https://ckknight.github.io/gorillascript/