Predavanje 8: Glave 25, 26

FlipView[{Text[Style[" Branko Urošević, imqfbrankourosevic@gmail.com ",20,"Text",Italic]],Text[Style[" Faculty of Economics, University of Belgrade, NBS and CESIfo Munich",20,"Text", Italic]]}]

25. Primena i mapiranje funkcije na liste

Uvod u primenu funkcija na izraze u WL: funkcija pre izraza

Postoji više načina na koji se neka glava (funkcija) f može primeniti na neki izraz x. Standardni metod je da napišemo f[x]. To smo videli ranije. Jasno je da imamo u vidu “primeniti funkciju f na x”. Alternativni način da se to uradi u WL jeste f@x.

f[x]

f@x

Ovo je posebno korisno ukoliko imamo više aplikacija funkcija jednu za drugom (složena funkcija):

f@g@h@x

Log@Abs@Sin@ (-3Pi/2)

Još jedan način za primenu funkcije na izraz: funkcija posle izraza

Postoji i treći način da se napiše f[x] u Wolfram Language: x//f. Primeni f posle x:

Primeniti funkciju f na x:

x//f

Primeniti na x prvo f pa g pa onda h

x//f//g//h

Naći numeričku aproksimaciju racionalnog broja 5/17

5/17//N

To je isto što i

N[5/17]

N@(5/17)

Primena funkcije na listu odjednom

Radeći sa Wolfram Language, veoma je korisno znati kako da se funkcije primenjuju na listu. Ukoliko primenimo f na celu listu odjednom, dobijamo, na primer:

f[{1,3,7}]

ili, što je isto,

f@{1,3,7}

ili

{1,3,7}//f

Vidimo da je glava ovog izraza f

Head[f@{1,3,7}]

Primena funkcije na svaki pojedinačni element proste liste

Pretpostavimo da smo na svaki pojedinačni element proste liste {1,3,7} primenili f. Dobili bismo izraz:

{f[1],f[3],f[7]}

Jedan način da se to uradi jednostavnije jeste da iskoristimo Table:

Table[f[k],{k,{1,3,7}}]

Drugi način je to uradimo koristeći operator mapiranja /@. Ovo znači: mapiraj (primeni) funkciju f na svaki element skupa definisanog listom:

f/@{1,3,7}

Evo i punog izraza za mapiranje:

Map[f,{1,3,7}]

Operator mapiranja ćemo koristiti veoma često pa je stoga važno da ga savladate.

Funkcija Framed

Framed je funkcija koja nacrta okvir oko nečega na šta smo je primenili. Na primer, ovako primenjujemo Framed na samo jedan element:

Framed[x]

ili, ekvivalentno:

Framed@x

Funkcija Framed primenjena na string

Framed@"Ovo je početak ozbiljnog učenja programiranja!"

Funkcija Framed primenjena na celu listu odjenom i poelementno

Framed (i svaku drugu funkciju) možemo da primenimo i na celu listu. Ukoliko bismo probali:

Framed[{1,2,3}]

ili

Framed@{1,2,3}

dobijamo uokvirenu celu listu. Sa druge strane, ukoliko mapiramo Framed na listu {1,2,3} dobijamo

Framed/@{1,2,3}

Map[Framed,{1,2,3}]

ili

Table[Framed[k],{k,{1,2,3}}]

Vidimo da funkcija Framed primenjena na neku listu nije isto što i funkcija Framed primenjena na svaki element liste ponaosob. Takva funkcije nije listabilna. Vidimo, takođe, da je od tri ekvivalentna načina za mapiranje ove funkcije na listu, prvi način ubedljivo najlakši i najelegantniji.

Primena funkcije Hue na listu

Istu stvar možemo da uradimo i sa drugim funkcijama. Na primer, ako primenimo funkciju Hue na listu brojeva {0.1,0.2,0.3,0.4} odjednom, dobijamo:

Hue[{0.1,0.2,0.3,0.4}]

što je isto što i

Hue@{0.1,0.2,0.3,0.4}

A ako ovu funkciju mapiramo na svaki pojedinačni element te liste dobijamo, na razne potpuno ekvivalentne načine, nešto sasvim drugo:

Hue/@{0.1,0.2,0.3,0.4}

Map[Hue,{0.1,0.2,0.3,0.4}]

Table[Hue[k],{k,{0.1,0.2,0.3,0.4}}]

Primena PieChart na složenu listu

Ako imamo listu koja se sastoji od podlisti,  /@ primenjuje (mapira) funkciju na svaku od podlista (na prvom nivou). Na primer, pretpostavimo da želimo da napravimo PieChart na više lista istovremeno. Evo jednog načina:

PieChart/@{{1,1,1,1,1,1,1},{1,1,1,4,4,4},{1,2,1,2,1,2}}

To je isto što i:

Map[PieChart,{{1,1,1,1,1,1,1},{1,1,1,4,4,4},{1,2,1,2,1,2}}]

Ako se PieChart primeni na celu složenu listu odjednom, dobijamo.

PieChart@{{1,1,1,1,1,1,1},{1,1,1,4,4,4},{1,2,1,2,1,2}}

Znači, PieChart daje drugi rezultat ukoliko se primeni na celu listu odjednom (to se zove nivo 0) ili ukoliko se primeni na podliste (to se zove nivo 1).

Primena funkcije Length na složenu listu

Evo kako slična ideja radi sa Length. Ako primenimo Length na složenu listu u celini, ona će svaku podlistu tretirati kao jedan element, pa ćemo dobiti da važi:

Length@{{a,a},{a,a,b,c},{a,a,b,b,b,b},{a,a,a},{c,c}}

pošto imamo 5 podlista u složenoj listi. Length u tom slučaju prosto broji podliste. Sa druge strane, ukoliko želimo da izračunamo dužinu svake od podlisti, moramo da mapiramo Length na prvi nivo:

Length/@{{a,a},{a,a,b,c},{a,a,b,b,b,b},{a,a,a},{c,c}}

ili

Map[Length,{{a,a},{a,a,b,c},{a,a,b,b,b,b},{a,a,a},{c,c}}]

Primena funkcije Graphics na listu grafika

Primenimo prvo Graphics na listu :

{Circle[],RegularPolygon[7],Style[RegularPolygon[3],Orange]}

u celini. Dobili bismo:

Graphics@{Circle[],RegularPolygon[7],Style[RegularPolygon[3],Orange]}

što je isto što i

Graphics[{Circle[],RegularPolygon[7],Style[RegularPolygon[3],Orange]}]

Kao rezultat dobijamo samo jedan grafik. Ukoliko pak mapiramo Graphics na listu grafičkih elemenata poelementno:

Graphics/@{Circle[],RegularPolygon[7],Style[RegularPolygon[3],Orange]}

Map[Graphics,{Circle[],RegularPolygon[7],Style[RegularPolygon[3],Orange]}]

dobijamo listu grafika, a ne jedan grafik. Stoga, Graphics nije listable funkcija. Iz tog razloga ni jedna funkcija čija je glava Graphics nije listabilna.

Primer funkcije koja se sama mapira na listu (Listable funkcija): Range

Šta bismo dobili ukoliko bismo Range primenili na neku listu brojeva u celini. To možemo uraditi na dva načina:

Range[{3,2,5,6,7}]

ili

Range/@{3,2,5,6,7}

Dobijamo potpuno isto.

Za razliku od prethodno razmatranih funkcija, funkcija Range je takozvana listabilna (Listable) funkcije koja se automatski mapira na liste. Ukoliko je funkcija Listable, to ćemo saznati kad primenimo pogledamo njene Attributes.

Attributes[Range]

Drugi primeri Listable funkcija: Sqrt, Log i većina drugih funkcija za rad sa brojevima

I neke druge funkcije imaju Attribute Listable, što znači da se automatski mapiraju na svaki pojedinačan element ma kako komplikovane liste.

Sqrt@{1,2,3,4}

Log@{1,2,3,4}

Sqrt@{{1,2,3,4},{5,6,7,8,9}}

Log@{{1,2,3,4},{5,6,7,8,9}}

Kad je funkcija Listable mapira se na sve nivoe.

Ali evo i primera funkcije koja radi sa brojevima ali nije Listable

Razmotrimo izuzetno važnu funkciju Mean. Ona izračunava srednju vrednost i radi kolonu po kolonu. Ukoliko matrica nije pravougaona, ovo neće moći da radi:

Mean[{{1,2,3,4},{5,6,7,8,9}}]

Ali ako nam je želja da nađemo srednje vrednosti za svaku od podlista, to možemo da uradimo jednostavno:

Mean/@{{1,2,3,4},{5,6,7,8,9}}

odnosno

Map[Mean,{{1,2,3,4},{5,6,7,8,9}}]

To je isto kao da smo izračunali

{Mean@{1,2,3,4},Mean@{5,6,7,8,9}}

Dakle, ako koristimo Map, po defoltu funkcija se mapira na podliste prvog nivoa.

Q and A

Zašto ne pišemo uvek f@x umesto f[x]?
f@x je ekvivalentno f[x], ali je, na primer, ekvivalent f[1+1] zapravo f@(1+1), a u tom slučaju f[1+1] je lakše da se razume. Nekad nam je zgodan jedan zapis a nekad drugi.

Zašto se /@ zove Map?
To dolazi iz matematike. Ako imamo skup {1, 2, 3}, f/@{1, 2, 3} može da se zamisli kao mapiranje (preslikavanje) tog skupa na neki drugi. 

Kako se kaže “//” and “/@”?
Tipično “slash slash” i “slash at”.

Kada mi trebaju zagrade sa @, // and /@?
To zavisi od pravila šta se prvo primenjuje. @ sjedinjuje jače nego +, pa f@1+1 znači f[1]+1 a ne f@(1+1) ili f[1+1]. // sjedinjuje slabije nego +, pa 1/2+1/3//N znači (1/2+1/3)//N.

Puno funkcija ima Listable Attribute pa se automatski mapiraju.

Range je listable, pa Range[{3, 4, 5}] znači isto što i Range/@{3, 4, 5}.

26. Čiste (anonimne) funkcije i njihova primena na izraze i liste

Šta su čiste (anonimne funkcije)

Do sada smo koristili samo funkcije koje postoje u WL. Ispostavlja se da je veoma lako proširiti skup funkcija. Jedan način da se to uradi jeste da se definišu takozvane čiste ili anonimne funkcije. To su funkcije koje nemaju neko posebno ime kao što je Plus, Sin, Total itd. One nemaju, zapravo, uopšte, ime. Zato se i zovu anonimne funkcije.

Pretpostavimo da želimo da napišemo čistu funkcije koja izračunama (x^2)+3 x-5. U punoj formi to bi bilo:

Function[{x},(x^2)+3x -5]

Ceo ovaj izraz definiše glavu funkcije! Evo kako bismo je primenili. Ako želimo da izračunamo vrednost te funkcije za x=4

Function[{x},(x^2)+3x -5][4]

ili, ekvivalentno

Function[{x},(x^2)+3x -5]@4

Vidimo da u punoj formi čista funcika počinje sa ključnom rečju Function. To govori o tome da funkcija nema neko drugo specifično ime. U telu funkcije prvo se definišu parametri funkcije (u ovom slučaju x), a onda, posle zareza, ide sama definicija šta funkcija treba da radi sa tim parametrima.

Čista funkcija može da se koristiti potpuno isto kao svaka druga funkcija u WL

Možemo predstaviti grafički ovu čistu funkciju.

Plot[Function[{x},(x^2)+3x -5][t],{t,-5,3}]

Možemo je primeniti i na svaki element liste

Function[{x},(x^2)+3x -5]/@{1,3,7}

To je, naravno, isto što i

{Function[{x},(x^2)+3x -5][1],Function[{x},(x^2)+3x -5][3],Function[{x},(x^2)+3x -5][7]}

Važno je samo da imena promenljivih u definiciji, to jest u { } budu ista kao i u telu funkcije. Kakve je ime u pitanju nije važno. Recimo, ovo radi potpuno isto kao i u slučaju kada smo koristili x u definiciji.

Function[{sunca},(sunca^2)+3sunca -5]/@{1,3,7}

Kraći (i mnogo češče korišćeni) oblike čiste funkcije

Primetimo da to što smo koristili x u definiciji funkcije ne igra nikakvu ulogu. Naime, promenljiva x je tu samo lokalna promenljiva i govori kakvu transformaciju parametra moramo da napravimo. Stoga se u praksi najčešće izbegava da se definiše uopšte parametar funkcije. Koristi se, umesto toga, kraći oblik čiste funkcije koji bi, u našem primeru, izgledao ovako:

(((#)^2)+3 #-5)&

Dakle, pišemo funkciju direktno koristeći  tarabu # kao mesto gde ide parametar. Na kraju stavimo znak ampersand &. Da je to potpuno ekvivalentno izrazu

Function[{x},(x^2)+3x -5]

možemo da proverimo sasvim jednostavno ovako (zagrada je tu važna zato što onda govori da se ampersan odnosi na sva tri člana funkcije).

(((#)^2)+3 #-5)&[4]

ili, ekvivalentno

(((#)^2)+3 #-5)&@4

Primena skraćenog oblika čiste funkcije

Primetimo da

Plot[(((#)^2)+3 #-5)&[t],{t,-5,3}]

a takođe

(((#)^2)+3 #-5)&/@{1,3,7}

U punoj formi možemo to napisati i ovako:

Map[(((#)^2)+3 #-5)&,{1,3,7}]

Čiste funkcije imaju ogromnu primenu u WL. Da bismo videli neke od najprostijih primena podsetimo se kako mapiramo neku funkciju na prostu listu

Kako da mapiramo Blur na listu kružnica

Pretpostavimo da imamo neke kružnice i da hoćemo da primenimo na svaku od njih Blur. To možemo da uradimo jednostavno preko Map odnosno /@.

Blur /@ {, , }

ili, u punoj formi

Map[Blur,{, , }]

Najzad, pošto je u pitanju prosta lista, možemo da primenimo i Table

Table[Blur[k],{k,{, , }}]

A kako da to izvedemo ako želimo da kontrolišemo nivo zamazivanja?

Ali, šta ukoliko želimo da ne koristimo defolt Blur nego da napravimo da je nivo zamazivanja jednak 5? To možemo da izvedemo jednostavno preko čistih funkcija. Evo kako:

Blur[#, 5] & /@ {, , }

Čista funkcija ovde je Blur[#, 5] &. Ovde je simbol # “slot” u koji ulazi argument (svaka od kružnica). Simbol & je tu da kaže da je to što smo imali pre toga zapravo čista funkcija. To je zapravo isto kao da smo uradili:

{Blur[, 5], Blur[, 5], Blur[, 5]}

Primetimo da umesto da koristimo tarabu za definiciju čiste funkcije mogli bismo to da napišemo i ovako. Umesto Blur[#, 5] & čista funkcija može da se napiše, na primer, ovako:

Function[{kruz},Blur[kruz, 5]]

pa bi onda primena na listu kružnica bila:

Function[{kruz},Blur[kruz, 5]]/@{, , }

Drugi načini da se napravi isto

Puna forma mapiranja bi bila:

Map[Blur[#, 5] &,{, , }]

Najzad, možemo, pošto je u pitanju prosta lista, da na nju mapiramo funkciju koristeći Table

Table[Blur[kruz, 5],{kruz,{, , }}]

Rotiranje liste stringova pomoću čistih funkcija

Svaki put slot pokazuje gde treba da svaki od elemenata liste da se nađe kada primenjujemo čistu funkciju:

Rotate string za 90°:

Rotate[#,90 Degree]&/@{"one","two","three"}

Šta je tu čista funkcija? To bi bio izraz Rotate[#,90 Degree]&. On rotira bilo koji string za 90 stepeni.

Ovo smo mogli napisati i na ove načine:

Map[Rotate[#,90 Degree]&,{"one","two","three"}]

kao i u punoj formi

Map[Function[{string},Rotate[string,90 Degree]],{"one","two","three"}]

A šta radi ova funkcija?

{Rotate[#,90 Degree],Rotate[#,45 Degree]}&/@{"one","two","three","four"}

Rotacija zadatog stringa za određeni broj stepeni

Naravno, mi možemo da koristimo čistu funkciju i tako da za zadati string rotiramo na različite načine. Naime:

Rotate["hello",#]&

definiše čistu funkciju koja rotira fiksnu reč “hello” za određeni broj stepeni. To je isto što i

Function[{stepeni},Rotate["hello",stepeni]]

Ukoliko sada primenimo

Rotate["hello",#]&[30 Degree]

Function[{stepeni},Rotate["hello",stepeni]][30 Degree]

Primena te funkcije na listu uglova rotacije

Naravno, možemo da primenimo tu funkciju na listu različitih uglova rotacije

Rotate["hello",#]&/@{30 Degree,90 Degree,180 Degree,220 Degree}

To je isto što i

Map[Rotate["hello",#]&,{30 Degree,90 Degree,180 Degree,220 Degree}]

ili

Function[{stepeni},Rotate["hello",stepeni]]/@{30 Degree,90 Degree,180 Degree,220 Degree}

Prikazati isti tekst u različitim bojama

Evo i čiste funkcije koja omogućava da prikažemo reč “hello” u različitim bojama, u fontu 20.

Style["hello",20,#]&

Na primer

Style["hello",20,#]&[Blue]

U punoj formi ta čista funkcija bi bila

Function[{boja},Style["hello",20,boja]]

iz čega sledi da:

Function[{boja},Style["hello",20,boja]]@Blue

Mapirati ovaj stil na listu različitih boja

Najprostiji način bi bio ovakav:

Style["hello",20,#]&/@{Red,Orange,Blue,Purple}

Možemo, kao i uvek, to predstaviti i na druge načine. Na primer:

Function[{boja},Style["hello",20,boja]]/@{Red,Orange,Blue,Purple}

ili koristeći koristeći Map ili Table

Map[Style["hello",20,#]&,{Red,Orange,Blue,Purple}]

Table[Style["hello",20,boja],{boja,{Red,Orange,Blue,Purple}}]

Slike različitih veličina

Pravimo kružnicu različitog radijusa:

Graphics[Circle[],ImageSize->#]&/@{20,40,30,50,10}

Nije teško naći druge načine da predstavimo to isto (svi su komplikovaniji od ovog)

Function[{radius},Graphics[Circle[],ImageSize->radius]]/@{20,40,30,50,10}

Map[Function[{radius},Graphics[Circle[],ImageSize->radius]],{20,40,30,50,10}]

ili,recimo

Table[Graphics[Circle[],ImageSize->radius],{radius,{20,40,30,50,10}}]

Koliko karaktera ima neki članak u Wikipedia?

Čista funkcija koja izračunava broj karaktera članka u Wikipediji bi mogao da se definiše ovako

StringLength[WikipediaData[#]]&

Kako izračunati dužinu članka za “apple”

StringLength[WikipediaData[#]]&["apple"]

U punoj formi ova funkcija bi bila, razume se,

Function[{naziv},StringLength[WikipediaData[naziv]]]

Function[{naziv},StringLength[WikipediaData[naziv]]]["apple"]

Brojanje karaktera za više članaka odjednom

Ovako možemo da izračunamo dužinu 3 različita članka iz Wikipedia:

StringLength[WikipediaData[#]]&/@{"apple","peach","pear"}

a ovako dobijamo tabelu koja se sastoji od parova imena članka i broja karaktera.

{#,StringLength[WikipediaData[#]]}&/@{"apple","peach","pear"}

Najzad, tu tabelu možemo da stavimo u Grid i da ga formatiramo prema želji

Grid[{#,StringLength[WikipediaData[#]]}&/@{"apple","peach","pear"},Frame-> All]

Isto to samo malo apstraktnije

Sada kada smo videli čiste funkcije u akciji, pogledajmo malo apstraktnije šta se tu događa. Možemo da stavimo slot gde god želimo u čistoj funkciji, koliko god puta to želimo. Evo jedne čiste funkcije

f[#,{x,#},{#,#}]&

Ovde su f i x proizvoljni simboli. Pretpostavimo da smo je primenili na simbol a.

f[#,{x,#},{#,#}]&[a]

U punoj formi ta čista funkcija bi bila definisana ovako

Function[{sim},f[sim,{x,sim},{sim,sim}]]

Pa bi onda imali:

Function[{sim},f[sim,{x,sim},{sim,sim}]]@a

Najzad, tu čistu funkciju možemo da primenimo na listu {a,b,c}

f[#,{x,#},{#,#}]&/@{a,b,c}

Map[f[#,{x,#},{#,#}]&,{a,b,c}]

Q and A

Odakle naziv  “čiste funkcije”?
Zato što sve što rade jeste da služe kao funkcije kada se na njih primene argumenti. Često se zovi i anonimne funkcije zato što, suprotno, recimo funkciji Blur, one nemaju ime. Stoga se na engleskom zovu u opštem slučaju  “pure anonymous functions”.

Zašto je potreban ampersand &?
Znak & indikuje je ono što smo prethodno naveli telo funkcije, a ne ime funkcije. f/@{1, 2, 3} gives {f[1], f[2], f[3]}, but f&/@{1, 2, 3} gives {f, f, f}.

Čiste funkciju su veoma važan deo funkcionalnog programiranja i jedan su od ključnih pojmova u WL.

Table[f[x], {x, {a, b, c}}] radi isto što i  f/@{a, b, c}. Vidite koliko je ovaj drugi način lakši, samo kad se malo naviknete. 

Budite pažljivi ukoliko imate više znakova & u istom izrazu! Nekada morate da koristite zagrade. I nekad je korisno koristiti i puni naziv čiste funkcije, naime, na primer, Function[x, x^2] umesto #^2&, kako bismo izbegli konflikte između raznih mesta gde se koristi # u raznim funkcijama..

Function[x,(x^2)][4]

#^2&[4]

Options (opcije) u funkcijama su često čiste funkcije.  Pri tome je važno staviti malu zagradu oko čiste funkcije inače neće biti interpretirana kako treba.