FUNKCIONÁLNÍ A LOGICKÉ PROGRAMOVÁNÍ 5. CVIČENÍ 2011 Jan Janoušek MI-FLP Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti
Page 1 of 5 Lekce 5 Obsah sekvence, sequencep, subseq, reverse, elt, nth, svref, char, sort, remove, reduce (:initialvalue, :from-end), position, position-if, :from-end, :start, :end, subst, nsubst, every, some, position, find, zásobník, push, pop, asociativní seznam, assoc (s :test a :key), eval, apply, funcall, format, princ, prin1, terpri, print, read, read-line, read-char Sekvence (posloupnost) Na seznam je možno pohlížet jako na posloupnost prvků. Seznam ovšem není jediný typ, se kterým je možné pracovat jako s posloupností. Lisp zavádí nadtyp sequence, kam kromě seznamů patří ještě vektory a řetězce (vektor znaků). Následující funkce je možno použít na jakýkoliv objekt typu sequence. (sequencep x) ==> vrací t jestliže hodnota argumentu x je typu sequence Funkce length vrací délku zadané posloupnosti. CL-USER 82 > (length '(1 2 3)) 3 CL-USER 83 > (length "abc") 3 Jestliže potřebujeme nějakou část posloupnosti, můžeme ji vytvořit pomocí subseq. Indexy se počítají opět od nuly. První parametr je posloupnost, druhý parametr index prvního vybíraného prvku, poslední (nepovinný) parametr představuje index prvního nepoužitého prvku. CL-USER 86 > (subseq '(1 2 3 4 5 6 7) 1 5) (2 3 4 5) Obrácení posloupnosti zajistí funkce reverse. CL-USER 90 > (reverse '(1 2 3)) (3 2 1) Uniformní přístup k prvkům pro všechny typy posloupností zajišťuje funkce elt. Pracuje stejně jako nth pro seznamy. Jestliže známe podtyp zpracovávané posloupnosti, je efektivnější volat speciální funkce (nth pro seznamy, svref pro vektory, char pro řetězce). CL-USER 97 > (elt "abcd" 1) #\b CL-USER 98 > (elt '(1 2 3) 1) 2 K řazení posloupností můžeme použít funkci sort. POZOR - jedná se o destruktivní funkci! To znamená, že může modifikovat seznam, který obdrží jako parametr. První parametr je řazená posloupnost, druhý parametr je funkce-predikát, podle něhož se posloupnost řadí. CL-USER 103 > (setf x '( 3 5 1 8 2)) (3 5 1 8 2) CL-USER 104 > (sort x #'>) (8 5 3 2 1)
Page 2 of 5 CL-USER 105 > x (8 5 3 2 1) ;! x nemá původní hodnotu, jestliže ji chceme zachovat - copy-list Podobně jako u member můžeme použít klíčový parametr :key, který udává funkci, podle jejíhož výsledku na každý prvek se provádí řazení. CL-USER 106 > (sort '((1 a) (5 c) (3 d)) #'> :key #'car) ((5 C) (3 D) (1 A)) K odstranění prvku z posloupnosti slouží funkce remove. Opět je možné použít :key a :test. CL-USER 40 > (remove 1 '((1 2) (2 3) (1 2) ( 3 1)) :key #'car ) ((2 3) (3 1)) Funkce reduce slouží k postupné redukci posloupnosti. První parametr je funkce, druhý posloupnost. Zadaná funkce musí mít dva parametry. Napřed je aplikována na první dva prvky posloupnosti, potom na výsledek a třetí prvek, výsledek a čtvrtý prvek,... Vrácena je hodnota posledního volání funkce. Volání (reduce #'fce '(1 2 3 4)) je ekvivalentní zápisu (fce (fce (fce 1 2) 3) 4) CL-USER 108 > (reduce #'intersection '((1 2 3 4 5 6) (2 3 4) (5 4 3))) (3 4) Toto chování se dá změnit pomocí klíčových parametrů. Nejčastěji využívané jsou parametry :from-end (mění pořadí provádění) a :initial-value (určuje hodnotu, která se použije při prvním volání funkce). Například pro převod řetězce na seznam znaků můžeme jednoduše napsat CL-USER 22 > (reduce #'cons "abcd" :from-end t :initial-value nil) (#\a #\b #\c #\d) Další užitečnou funkcí pro práci s posloupnostmi je funkce position. Vrátí pozici hledaného prvku v posloupnosti. Funkce position-if vrátí index prvku, který první splnil danou podmínku. CL-USER 94 > (position #\b "abrakadabra") 1 CL-USER 95 > (position-if #'zerop '(1 2 3 0 9 8 0)) 3 Většina funkcí, které pracují s posloupnostmi, umí pracovat s klíčovými parametry. Mimo již zmíněných :key a :test jsou to ještě: :from-end - udává, zda se má posloupnost brát od konce (default nil) :start - index prvku, na kterém se má začít (default 0) :end - index posledního prvku Další funkce pro práci s posloupnostmi: subst, nsubst, every, some, position, find
Page 3 of 5 Zásobník Seznam můžeme využít jako zásobník. K ukládání a vybírání ze zásobníku jsou v Lispu definován a makra push a pop. Výraz (push obj lst) můžeme přepsat jako (setf lst (cons obj lst)) Výraz (pop lst) je ekvivalentní s: (let ((tmp (car lst))) (setf lst (cdr lst)) tmp) CL-USER 48 > (setf x nil) CL-USER 49 > (push 'a x) (A) CL-USER 50 > (push '(b) x) ((B) A) CL-USER 51 > (pop x) (B) Asociativní seznam Cons buňka představuje přirozený způsob, jak reprezentovat dvojici klíč-hodnota. Asociativní seznam můžeme v Lispu vytvořit jako seznam cons buněk. CL-USER 15 > (defparameter *help* '((list. "vytvareni seznamu") (defun. "definice funkci") (+. "scitani") (if "vetveni" "spec. operator"))) *HELP* CL-USER 18 > (defun simple-help (symb) (assoc symb *help*)) SIMPLE-HELP CL-USER 19 > (simple-help 'list) (LIST. "vytvareni seznamu") CL-USER 20 > (simple-help 'if) (IF "vetveni" "spec. operator") CL-USER 21 > (simple-help 'append) Pro přístup k jednotlivým položkám slouží funkce assoc. Jestliže je daný klíč nalezen, je navrácena celá cons buňka, v opačném případě nil. Protože seznam je tvořený z cons buněk, je ho možné použít namísto tečka-dvojice (viz if v předchozím případě). Podobně jako u member můžeme i u funkce assoc specifikovat operaci, která provede porovnání (:test), a operaci, která se před porovnáním aplikuje na každý prvek (:key). CL-USER 24 > (assoc '+ '(((+ add) scitani) ((- sub) odcitani)) :key #'car) ((+ ADD) SCITANI)
Page 4 of 5 Eval Již víme, že program je v Lispu zapsán ve formě seznamu. Umíme také se seznamy pracovat. Dosud nám však chyběla funkce, které bychom zadali seznam a ona by ho vyhodnotila. Takovou funkcí je právě eval. CL-USER 35 > (eval '(+ 1 2 3)) 6 Použití eval je výhodné v případech, kdy nemůžeme použít apply nebo funcall. CL-USER 42 > (apply #'and '(t t nil t)) Error: Syntactic error in form (FUNCTION AND): Can't use FUNCTION on the special form AND. Ohlásila se chyba, protože and není funkce ale makro. S eval proběhne výpočet bez problémů: CL-USER 44 > (eval (cons 'and '(t t nil t))) Funkce eval má však i nevýhody. Protože pracuje se seznamy vytvořenými v programu, je její volání pomalejší. Seznam se buď vyhodnocuje interpretovaným způsobem nebo se musí v daném místě při provádění kompilovat. Obojí snižuje výkon. Výraz v eval je navíc vyhodnocen bez lexikálního kontextu! CL-USER 48 > (setf x 10) 10 CL-USER 49 > (let ((x 15)) (eval (cons 'identity '(x)))) 10 Vstup / výstup Základní funkcí pro zajištění textového výstupu je v Lispu funkce format. Očekává dva nebo více argumentů: první určuje, kam bude směřovat výstup; druhý parametr je formátovací řetěz, který ovlivňuje výstupní podobu zadaných jako ostatní parametry. CL-USER 3 > (format t "~A - ~A je ~A~%" 3 2 (- 3 2)) 3-2 je 1 V předchozím příkladu je "3-2 je 1" vytištěný výstup a nil je návratová hodnota funkce format. Jestliže prvním parametrem funkce format je t, posílá se výstup na *standard-output*, což je standardně top-level. Místo t můžeme použít kterýkoliv výstupní stream. Jestliže použijeme na místě prvního parametru nil, je navrácenou hodnotou funkce přímo vytvořený řetězec. CL-USER 68 > (format nil "~A - ~A je ~A" 3 2 (- 3 2)) "3-2 je 1" Kromě direktivy ~A, která tiskne výstup jako funkce princ (viz dále), můžeme použít ještě ~S (výstup jako prin1) a ~F (tisk čísel s řádovou čárkou - viz specifikace). CL-USER 76 > (format t " ~S ~A " "ahoj" "ahoj") "ahoj" ahoj
Page 5 of 5 Další funkce pro výstup jsou prin1 a princ. CL-USER 73 > (prin1 "Hello World") "Hello World" "Hello World" CL-USER 74 > (princ "Hello World") Hello World "Hello World" Rozdíl mezi nimi je ten, že funkce prin1 tiskne výstup v takové podobě, aby bylo možné jej použit opět na vstupu. Text je obalen uvozovkami, neobvyklé znaky uvnitř atomů způsobí uzavření jména mezi "svislítka". Obě funkce mají jako druhý nepovinný parametr výstupní stream. Funkce terpri vytiskne na výstup znak nový řádek. Funkce print je stejná jako prin1, vytiskne však před vlastním objektem znak nový řádek. Pro čtení ze vstupu se nejčastěji používají následující funkce: read, read-line a read-char. Všechny tyto funkce očekávají nepovinný argument vstupní stream, jestliže je vynechán, uvažuje se *standard-input*. Funkce read načítá jeden symbolický výraz, a ten potom vrátí jako výsledek. Stejnou funkci používá interpret Lispu pro čtení výrazů zadávaných k vyhodnocení. CL-USER 84 > (read) ( 1 2 a b) (1 2 A B) Funkce read-line načítá všechny znaky až do konce řádku a pak je vrátí jako řetězec. CL-USER 85 > (read-line) 1. radka textu "1. radka textu" Funkce vrací dvě hodnoty, druhá je t pouze v případě, že funkce read-line vypotřebovala všechny znaky ze vstupu a nenarazila na znak nový řádek. Kromě vstupního streamu můžeme nastavit další nepovinné parametry: jestli vracet v případě dosažení konce souboru chybu nebo ne a co vrátit v případě, že je předchozí parametr nil. Tyto parametry jsou užitečné při načítání ze souboru. Poslední funkce read-char načte ze vstupu znak a vrátí ho jako výsledek. Pomocí funkcí read a eval můžeme napsat zjednodušenou obdobu interpretu v Lispu. (defun top-level () (print "my top >") (print (eval (read))) (top-level))
Jazyky pro umělou inteligenci - cvičení file:///c:/pracovni/vyuka/jui/web/w05test.html Page 1 of 1 Cvičení 5 1) Definujte funkci spoj, která obdrží jako své argumenty dva seznamy. Výsledkem je seznam vzniklý spojením těchto dvou seznamů. (spoj '(1 2 3 a b) '(c d e 4)) --> (1 2 3 A B C D E 4) 2) Pomocí funkce spoj (viz 1) a funkce reduce definujte funkci my-append, která obdrží jako své parametry libovolný počet seznamů a vytvoří z nich seznam jeden. Promyslete, v jakém pořadí bude nejvýhodnější předávat argumenty funkci spoj (parametr :from-end funkce reduce -pořadí bude záviset na definici funkce spoj). 3) Definujte funkci my-adjoin (obdoba standardní funkce adjoin), která bude umět pracovat s klíčovým parametrem :test. Můžete využít toho, že funkce adjoin očekává stejné klíčové argumenty jako funkce member. (Nápověda: apply, &rest). 4) Definujte funkci transp, ktera transponuje matici zapsanou seznam řádkových seznamů ve tvaru ((a11 a12... a1n) (a21 a22... a2n)... (am1 am2...amn)). (transp '((a11 a12 a13)(a21 a22 a23)(a31 a32 a33))) --> ((A11 A21 A31)(A12 A22 A32)(A13 A23 A33)) 5) Definujte funkci vloz, která vloží prvek do seznamu na pozici určenou indexem. (vloz 'a 0 '(1 2 3 4)) --> (a 1 2 3 4) (vloz 'a 10 '(1 2 3 4)) --> (1 2 3 4 a) 6) a) Definujte funkci all-ok?, která obdrží jako parametr jméno funkce (s jedním parametrem) a seznam. Funkce se postupně volána se všemi prvky ze seznamu jako argumenty. Funkce all-ok? vrátí t, jestliže všechna volání zadané funkce vrátila t, jinak vrátí nil. b) Rozšiřte na funkce s libovolným počtem parametrů. (all-ok?2 #'(lambda (x y) (and (evenp x) (oddp y))) '(2 4 6) '(1 3 6)) --> 7) Definujte funkci occ-count, která očekává jako jediný parametr seznam atomů. Výstupem funkce je seřazený (podle četnosti výskytu) seznam tečka-dvojic, určující četnost výskytu jednotlivých elementů. (occ-count '(a b a f b a c d a f d b a)) --> ((A. 5) (B. 3) (D. 2) (F. 2) (C. 1)) 8) Definujte funkci chci-cislo, která (s vhodným promptem) čte vstup opakovaně tak dlouho, dokud na vstup neobdrží číslo, které vrátí jako výsledek. 9) Definujte funkci print-dot, která se zeptá na číslo a vytiskne tolik teček na standardní výstup..