Predavanje 13: Glave 33

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]]}]

33. Izrazi i njihova struktura

Uvod o izrazima u WL

Sve u WL je simbolički izraz, koji može da se sastoji od najjednostavnijih izraza (atoma) ili vrlo složenih izraza koje su kombinacije jednostavnih simboličkih izraza. Slažemo ih kao Lego kocke. Već smo videli puno toga u WL: liste, grafici, čiste funkcije itd. Sve te stvari i, zapravo, sve što čini WL, jesu simbolički izrazi (symbolic expressions).

Simbolički izraz je vrlo opšti način na koji se može predstaviti bilo šta. f[x,y] je prosti primer jednog simboličkog izraza. Sami po sebi, simbolički izrazi nemaju nikakvo  značenje osim ako im ne definišemo pravila ponašanja. Probajmo ovo:

f[x,y]

Pošto f nije definisano kao neka funkcija, samo nam daje ponovo isti taj izraz. {x,y,z} je još jedan primer simboličkog izraza. Njegovo FullForm je List[x,y,z]:

List[x,y,z]

Funkcionalno programiranje je slaganje izraza jedan u drugi kao kockice

Simbolički izrazi se mogu ubaciti jedan u drugi. Na primer, složena lista se dobije kada se unutar neke liste umetne jedna ili više lista.

List[List[a,b],List[c,d]]

FullForm je interna forma simboličkog izraza:

FullForm[{{a,b},{c,d}}]

Na primer, Graphics[Circle[{0,0}]] je simbolički izraz koji prikazuje sliku kružnice. FullForm daje internu strukturu izraza. Ovaj simbolički izraz prikazuje kružnicu:

Graphics[Circle[{0,0}]]

FullForm[]

Primeri simboličkih izraza koji koriste aritmetiku

Simbolički izrazi se često koriste za izračunavanja. Na primer:

Plus[2,2]

FullForm[Plus[2,2]]

Po internim pravilima, svi aritmetički izrazi u WL se odmah izvrše (osim ako program nekom specijalnom naredbom ne sprečimo da to uradi). Zato kao pun izraz dobijamo već uprošćen odnosno izračunat izraz. U sledećem primeru elementi liste budu izračunati, ali lista je kao takva i simbolička (zapravo su i svi brojevi simboli koji se zovu atomi, ali o tom potom):

{Plus[3,3],Times[3,3],Power[3,3]}

FullForm[{Plus[3,3],Times[3,3],Power[3,3]}]

Malo složeniji izrazi

Ovo je simbolički izraz koji razmazuje sliku kruga:

Blur[,5]

Kako izgleda taj izraz u punoj formi?

Blur[,5]//FullForm

Možete da ovo napišete i na sledeći način (sve simbolički):

Blur[Graphics[Circle[{0,0}]],5]

Najmanji elementi svakog izraza su atomi

Šta su ultimativne kockice iz kojih se grade simbolički izrazi? To su atomi (atoms). Dobili su naziv po osnovnom gradivnom materijalu prirode. Osnovni atomi su brojevi, stringovi, simboli, grafovi. Da se sazna da li je nešto atom koristimo AtomQ. Ukoliko je odgovor True, to je atom. Ako nije, u pitanju je složeni objekat:

AtomQ/@{"string",x,Pi,9,9.13,,,Log[x]}

Dakle, od svih ovih objekata, dva poslednja izraza nisu atomi. Ostali jesu.

Brojevi, stvari kao su x, y, f, Plus, Graphics i Table su simboli. Svaki simbol ima svoje ime. Nekada simboli imaju predefinisani smisao. Nekada se mogu i izračunati. Nekada to je samo deo definisane strukture koju druge funkcije mogu da koriste. 

Wolfram Language može da koristi simbole direktno umesto da ih prvo pretvara u brojeve. Ovo je jedna od osnovnih osobina WL i time se razlikuje veoma u odnosu na Java, C, R, ili MATLAB, na primer.

Simboličko izračunavanje u WL

U Wolfram Language, x može da bude prosto x, bez da moramo pre toga da ga definišemo:

x

i sva svojsta algebre važe:

x+z+y+x

Ovo se može u punoj formi predstaviti i ovako:

Plus[x,z,y,x]

Ovo deluje trivijalno, ali najveći broj standardnih programa tako nešto onemogućava. Ovo je veoma moćno.

Ako imamo simbole  x, y, f, možemo da napravimo beskonačno mnogo različitih drugih izraza sa njima. Na primer,  f[x], and f[y], and f[x,y]. Takođe i f[f[x]] ili f[x,f[x,y]], ili, na primer, x[x][y,f[x]] ili šta god.

TreeForm

Da bi se grafički videla struktura nekog izraza može da se predstavi preko stabla (tree) gde su krajni listovi atomi. To se radi pomoću TreeForm.

{f[x,f[x,y]],{x,y,f[1]}}

TreeForm[{f[x,f[x,y]],{x,y,f[1]}}]

Pogledajmo TreeForm izraza vezanog za grafiku:

Graphics[{Circle[{0,0}],Hue[0.5],Disk[{1,1}]}]

TreeForm[Graphics[{Circle[{0,0}],Hue[0.5],Disk[{1,1}]}]]

Pošto su izrazi vrlo uniformni po strukturi, operacije u WL se mogu obavljati na isti način. Svaki izraz ima delove (parts) kao i lista, i možemo da uradimo na primer:

List[x,y,z][[2]]

Delovi izraza

Ovde je samo glava drugačija, umesto List imamo f

TreeForm[f[x,y,z]]

Možemo da izvadimo neki deo isto kao da radimo sa listom:

f[x,y,z][[2]]

Ovim dobijamo koordinate centra kružnice:

TreeForm[Graphics[Circle[{0,0}]]]

Graphics[Circle[{0,0}]][[1,1]]

Head (glava) izraza

Svaki izraz u WL, kao što već, nadam se, dobro znamo, počinje sa glavom izraza. Glava se nalazi na nultom nivou izraza (na vrhu u TreeForm). Glava liste je, razume se, List:

Head[{x,y,z}]

Glava se nalazi na vrhu TreeForm

TreeForm[{x,y,z}]

Head[Graphics[Circle[{0,0}]]]

TreeForm[Graphics[Circle[{0,0}]]]

Svaki deo izraza ima Head, čak i atomi. Head za ceo broj je Integer:

Head/@{14, 13.4,2/3,2+3 I,"hello",x}

Ako koristimo TreeForm, glava izraza stoji na samom vrhu

Head[f[{x,y,z}]]

TreeForm[f[{x,y,z}]]

Čiste funkcije kao glave izraza

Ovo je puna forma čiste funkcije (# is Slot[1]):

FullForm[#^2&]

A ovo je TreeForm te iste funkcije

TreeForm[#^2&]

Sama čista funkcija ima svoju glavu (to je Function). Ali i cela čista funkcija ima funkciju glave izraza. Ovako primenjujemo čistu funkciju sa simbol b.

Function[Power[Slot[1],2]][b]

Čista funkcija sa jednom vektorskom promenljivom

Napisati čistu funkciju koja množi elemente liste koja se sastoji od para brojeva. Za to nam je potrebno da znamo kako da definišemo vektorsku čistu funkciju sa dve komponente. Prva komponenta  bi bila #[[1]] a druga komponenta #[[2]]. Stoga bismo imali da je odgovajuća čista funkcija

#[[1]]#[[2]]&@{a,b}

ili, u punoj formi

Function[{x},Part[x,1]Part[x,2]]

Važno je napomenuti da ovde imamo samo jednu promenljivu, ali ona nija skalarna nego lista dimenzije 2. Proveravamo da to radi

Function[{x},Part[x,1]Part[x,2]]@{a,b}

Napisati čistu funkciju koja za listu dimenzije 2 sabere kvadrate njenih elemenata

(((#[[1]])^2)+((#[[2]])^2))&@{a,b}

Function[{x},((Part[x,1])^2)+((Part[x,2])^2)]@{a,b}

Select i Cases se mogu predstaviti i kao glave čiste funkcije

Pretpostavimo da želimo da izdvojimo iz neke liste brojeve veće od 4. To možemo da napravimo preko Select.

Select[{1,2.2,3,4.5,5,6,7.5,8},#>4&]

Tа operacija se može da se predstavi i na sledeći način, gde je Select glava čiste funkcije Select[#>4&] :

Select[#>4&][{1,2.2,3,4.5,5,6,7.5,8}]

Head[Select[#>4&]]

Ovde prvo izdvoljimo brojeve veće od 4 koristeći Select a onda iz dobijene liste izdvoljimo samo cele brojeve. Kako Cases tako i Select se ovde pojavljuju kao Heads:

Cases[_Integer]@Select[#>4&]@{1,2.2,3,4.5,5,6,7.5,8}

Na drugi način

Cases[Select[{1,2.2,3,4.5,5,6,7.5,8},#>4&],_Integer]

Sve osnovne operacije koje smo videli da rade na listama rade i sa proizvoljnih glavama izraza

Sve osnovne strukturne operacije koje smo videli da rade na listama rade na proizvoljnim izrazima. Na primer:

Length[f[x,y,z]]

Drop[f[x,y,z],1]

Part[f[x,y,z],2]

f[x,y,z][[2;;3]]

Select[f[1,2,3,4,5],#>2&]

Ali, neke specifičnije funkcije ne rade tako (a i ne bi trebalo da tako rade) jer za proizvoljnu funkciju f. Ovo nije definisano unapred za svako f:

Mean[f[{x,y,z}]]

a ovo jeste

f[Mean[{x,y,z}]]

Više o mapiranju funkcije pomoću Map na nivou 1 (to je isto što i operator /@)

Operacija /@ (odnosno Map na prvom nivou) radi ne samo kada je glava List, nege i kada je glava neki drugi izraz,

f/@g[x,y,z]

To je isto što i:

Map[f,g[x,y,z]]

Ukoliko želimo da mapiramo funkciju f na

Plus[x,y,z]

Dobijamo

Map[f,Plus[x,y,z]]

Na sličan način ako želimo da mapiramo funkciju f na

Times[x,y,z]

Map[f,Times[x,y,z]]

Mapiranje na nivou 1

Razmotrimo sledeći izraz

g[h1[x,y],h2[x,y]]

TreeForm[g[h1[x,y],h2[x,y]]]

Mapirajmo, na nivou 1, funkciju Framed na taj izraz.

Framed/@g[h1[x,y],h2[x,y]]

Framed/@g[h1[x,y],h2[x,y]]//TreeForm

U punoj formi to bi se pisalo

Map[Framed,g[h1[x,y],h2[x,y]]]

Kako proveriti koliko izraz ima nivoa i koji su to nivoi

Kako da proverimo kolika je dubina nekog izraza, odnosno koliko ima nivoa. Jedan način je da napravimo TreeForm

TreeForm[{{{a,b},{c,d},{e,f}}}]

Evo kako možemo da vidimo nulti, prvi, sve do trećeg (najdubljeg) nivoa:

Level[{{{a,b},{c,d},{e,f}}},{0}]

Evo još jednog izraza:

TreeForm[g[h1[x,y],h2[x,y]]]

Level[g[h1[x,y],h2[x,y]],{0}]

Kontrola nivoa na kome se primenjuje Map

Po defoltu, Map se primenjuje na prvom nivou. Na primer, ova dva izraza su ekvivalentna:

Map[Reverse,{{a,b},{c,d},{e,f}}]

Map[Reverse,{{a,b},{c,d},{e,f}},{1}]

Može da koristimo Map i na višim nivoima (dublje). Na primer, ovako možemo izmeni mestima položaj u podlistama na 2. nivou

Map[Reverse,{{{a,b},{c,d},{e,f}}},{2}]

Mapirajmo funkciju Framed na drugom nivou

Map[Framed,{{{a,b},{c,d},{e,f}}},{2}]

A ovako bismo mapirali funkciju koja množi parove simbola na 2. nivou:

Map[#[[1]]#[[2]]&,{{{a,b},{c,d},{e,f}}},{2}]

Malo apstraktije mapiranje  u TreeForm (menjamo nivo od 0 do 3)

Map[f,{{{a,b},{c,d},{e,f}}},{0}]//TreeForm

Skraćena forma primene funkcije Apply na nivou 0: @@

Map se koristi da dodamo neku glavu svakom elementu izraza na određenom nivou. Apply se koristi da se promeni glava izrazu na određenom nivou. Na primer, pretpostavimo da smo Apply glavu h na izraz g[a,b,c].  Tada je glava g zamenjena sa glavom h. To se, po defoltu, događa na nivou 0.

h@@g[x,y,z,w]

Ako apply funkciju f na listu, glava List biće zamenjena glavom f

f@@{x,y,z}

To je isto što i

f@@List[x,y,z]

Ukoliko pođemo od liste

List[a,b,c,d]

elemente možemo da saberemo tako što bismo apply glavu Plus na listu List[a,b,c,d]

Plus@@List[a,b,c,d]

Kako oduzeti dva elementa u listi

Znamo da

Subtract[x,y]

Ali, ako probamo

Subtract[{x,y}]

Da bismo oduzeli dva elementa u listi uradimo

Subtract@@{x,y}

ili u punoj formi

Apply[Subtract,{x,y},{0}]

Po defoltu, to je isto što i

Apply[Subtract,{x,y}]

Nastavak o Apply na nivou 0

Razmotrimo kako da glavu f zamenimo sa glavom koju smo stavili u Apply

Apply[Times,f[a,b,c,d]]

Možemo kombinovati Apply i Map. Na primer, pretpostavimo da polazeći od izraza f[a,b,c,d] želimo da svaki od argumenata unutar f bude logaritmovan, a da se onda logaritmi ta 4 argumenta saberu. Prvo primenimo Map

Log/@f[a,b,c,d]

a onda na to i Apply na nivou 0

Plus@@Log/@f[a,b,c,d]

ili u punoj formi

Apply[Plus,Map[Log,f[a,b,c,d],{1}],{0}]

Još primera primene Apply na nivou 0

Evo kako možemo da iskoristimo Apply na nivou 0 da stvorimo pravilo (Rule):

#1->#2&@@{x,y}

Apply[#1->#2&,{x,y}]

Evo naravno prostije alternative:

Rule@@{x,y}

Apply[Rule,{x,y}]

Probajmo sada da razmotrimo kako da koristimo Apply na listi od liste. Na nivou 0 (default) dobijemo sledeće:

Apply[f,{{x,y},{a,b}}]

Primena Apply na nivou 1: @@@

Pretpostavimo da imamo složeni izraz, recimo listu, od dva nivoa (matricu, recimo). Vrlo je česta situacija da hoćemo da zamenimo unutrašnju listu sa nekom funkcijom. Evo jednostavnog shortcuta za to: @@@. To je isto što i Apply na nivou 1. 

Zamenimo glave unutrašnjih lista sa f:

f@@@{{1,2,3},{4,5,6}}

To je isto što i

Apply[f,{{1,2,3},{4,5,6}},{1}]

ili

{f@@{1,2,3},f@@{4,5,6}}

Primetimo da ovo nije isto (ovo je Map na nivou 1):

f/@{{1,2,3},{4,5,6}}

Ovo poslednje je ekvivaletno izrazu:

Map[f,{{1,2,3},{4,5,6}}]

Primeri za primenu Apply na različitim nivoima

Ovako možemo da unutrašnje liste pretvorimo u pravila:

Rule@@@{{x,y},{z,w},{q,r}}

Apply[Rule,{{x,y},{z,w},{q,r}},{1}]

Ovako možemo da pomnožimo svaki od parova

Apply[Times,{{x,y},{z,w},{q,r}},{1}]

To funkcioniše na isti način ako u pitanju proizvoljni izrazi. Recimo:

Apply[f,g[h1[x,y],h2[x,y]],{0}]

Apply[f,g[h1[x,y],h2[x,y]],{1}]

Apply čistu funkciju (((#[[1]])^2)+((#[[2]])^2))&, što je suma kvadrata elemenata liste, na listu {{a,b}}

Apply[(((#[[1]])^2)+((#[[2]])^2))&,{{a,b}},{0}]

Kombinovanje Apply i Map

Dignimo svaki od elemenata na kvadrat, pa onda svaki par saberimo. Jedan način da svaki element dignemo na kvadrat pojedinačno jeste da primenimo Map na funkciju koja će kvadrirati svaki broj. Za to možemo koristiti čistu funkciju ((#)^2)&

Map[((#)^2)&,{{x,y},{z,w},{q,r}},{2}]

Sada možemo da saberemo svaki tako dobijeni par

Apply[Plus,Map[((#)^2)&,{{x,y},{z,w},{q,r}},{2}],{1}]

To možemo i jednostavnije ovako:

Map[(((#[[1]])^2)+((#[[2]])^2))&,{{x,y},{z,w},{q,r}},{1}]

Kada saberemo kvadrate po parovima, možemo na svaki zbir kvadrata primeniti Log

Map[Log,Apply[Plus,Map[((#)^2)&,{{x,y},{z,w},{q,r}},{2}],{1}]]

ili, puno kraće

Map[Log[((#[[1]])^2)+((#[[2]])^2)]&,{{x,y},{z,w},{q,r}},{1}]

Funkcija Thread

Thread se razlikuje od Map i Apply jer umesto jedne uzima dve liste kao argument. One moraju biti istih dimenzija. Potrebna nam je i funkcija koju želimo da threadujemo. Thread, onda  uzima prvi element prve i druge liste i primenjuje na njih funkciju, onda to isto radi sa drugim elementom iz obe liste, itd. Kao rezultat dobijamo jednu listu iste dužine kao originalna lista:

Thread[g[{a,b,c},{x,y,z}]]

Ovo je suma dva vektora:

Thread[Plus[{a,b,c},{x,y,z}]]

Ovo je drugi, duži način da napišemo to isto:

List[Plus[a,x],Plus[b,y],Plus[c,z]]

Ovako možemo da množimo dve liste po-elementno:

Thread[Times[{a,b,c},{x,y,z}]]

Još primera funkcije Thread

Na sledeći način možemo da napravimo Rule pomoću Thread

Thread[Rule[{a,b,c},{x,y,z}]]

Ovo se može predstaviti i ovako

Thread[{a,b,c}-> {x,y,z}]

Ovako možemo da definišemo više uslova jednakosti:

Thread[Equal[{a,b,c},{x,y,z}]]

Thread[{a,b,c}=={x,y,z}]

ili nejednakosti

Thread[GreaterEqual[{a,b,c},{x,y,z}]]

Thread[{a,b,c}≥  {x,y,z}]

MapThread

Puno sličnih stvar može da se uradi sa MapThread, ali je samo sintaksa malo drugačija. Ova funkcija kombinuje Map i Thread pa se zato i zove: MapThread

MapThread[g,{{a,b,c},{x,y,z}}]

Primetimo da ovo možemo da uradimo i malo drugačije. Naime, to je ekvivalntno tome da prvo transponujemo matricu i da onda Apply funkciju g  tako transponovanu matricu na nivou 1. Transponovanje matrice daje:

Transpose[{{a,b,c},{x,y,z}}]

Onda Apply g do nivoa {1}

Apply[g,Transpose[{{a,b,c},{x,y,z}}],{1}]

Pretpostavimo da želimo da saberemo prve elemente iz obe podliste, pa onda druge elemente. To dobijamo ovako:

MapThread[Plus,{{a,b,c},{x,y,z}}]

Isto to dobijamo i koristeći čistu funkciju sa dva argumenta, po jedan za svaku od podliste, dobijamo (obratite pažnju da ovo nije funkcija sa vektorskim argumentom dimenzije 2 nego funkcija dva skalarna argumenta):

MapThread[#1+#2&,{{a,b,c},{x,y,z}}]

Ukoliko želimo da nađemo sumu kvadrata prvih elemenata, pa drugih i td, onda se jednostavno napiše

MapThread[((#1)^2)+((#2)^2)&,{{a,b,c},{x,y,z}}]

Primena Thread i MapThread

Razmotrimo sada neke zanimljivije primere. Razmotrimo kružnicu. Po defoltu:

Graphics[Circle[{0,0},1]]

Pretpostavimo sada da imamo listu različitih radijusa:

radii={0.5,1.0,0.5};

a da su centri takođe u različitim tačkama:

centers={{-1,0},{0,0},{1,0}};

Na sledeći način možemo da napravimo više kružnica odjednom:

Graphics[Thread[Circle[centers,radii]]]

ili

Graphics[MapThread[Circle,{centers,radii}]]

Listable (listabilnost funkcije)

Mnoge postojeće funkcije imaju Listable attribute. Takve funkcije se automatski mapiraju. Razmotrimo Log:

Log[{a,E,1}]

Attributes[Log]

Listable funkcije koje imaju dva ili više argumenta automatski threaduju:

Attributes[Plus]

Plus[{1,2,3},{4,5,6}]

To je isto kao da smo uradili

MapThread[Plus,{{1,2,3},{4,5,6}}]

Kako definisati funkciju da bude Listable

Funkcije možemo da napravimo da budu Listable. Na primer, uzmimo proizvoljnu funkciju g.

g[{x,y,z}]

SetAttributes[g,Listable]

?g

g[{x,y,z}]

Kada smo završili, za svaki slučaj treba da vratimo stanje na početak:

Clear[g]

Q and A

Kako se interpretiraju @@ i @@@ ?
f@@expr je Apply[f, expr]. f@@@expr je Apply[f, expr, {1}]. Obično se člitaju kao “double at” and “triple at”.

Stvari kao što su Select[f]  koje treba da deluju na izraze nazivaju se operatorske forme, po analogiji sa operatorima u matematici. Korišćenje Select[f][expr] umesto Select[expr, f] se često zove currying, po logičaru Haskell Curry.

Simboli kao što je x mogu se koristiti da predstave algebarske varijable ili nepoznate. Ovo je ključ za rad na matematici u WL. 

LeafCount daje ukupni broj atoma u expression tree. ByteCount daje ukupni broj bytes potrebnih da se skladišti izraz.

ByteCount[Plot[Sin[x],{x,0,2 π}]]

Pick takođe može da izdvaja iz liste elemente koji zadovoljavaju neki uslov, ali je opštije u odnosu na Select. Pick[expr, secList] vraća one elemente u listi za koje je setList jednak True.

Pick[{a,b,c,d,e},{True,False,True,False,False}]

Umesto True i False možemo da koristimo 0 i 1.

Pick[{a,b,c,d,e},{1,0,1,0,0},1]