INF2810: Funksjonell programmering: Mer om Scheme. Rekursjon og iterasjon.

Like dokumenter
INF2810: Funksjonell Programmering. Mer om Scheme. Rekursjon og iterasjon.

INF2810: Funksjonell Programmering. Mer om Scheme. Rekursjon og iterasjon.

INF2810: Funksjonell Programmering. Mer om Scheme. Rekursjon og iterasjon.

Rekursjon og lister. Stephan Oepen & Erik Velldal. 1. februar, Universitetet i Oslo

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

INF2810: Funksjonell Programmering

Høyere-ordens prosedyrer

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. Kommentarer til prøveeksamen

INF2810: Funksjonell Programmering. Lokale variabler. Og trær.

INF2810: Funksjonell Programmering. En metasirkulær evaluator

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. En metasirkulær evaluator

INF2810: Funksjonell Programmering

INF2810: Funksjonell Programmering. Eksamensforberedelser

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme

INF2810: Funksjonell programmering: Introduksjon

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme, del 2

INF2810: Funksjonell Programmering. En metasirkulær evaluator, del 2

INF2810: Funksjonell Programmering. En metasirkulær evaluator, del 2

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme, del 2

INF2810: Funksjonell Programmering. En Scheme-evaluator i Scheme, del 2

INF2810: Funksjonell Programmering. Strømmer og utsatt evaluering

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell programmering: Introduksjon

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Trær og mengder

INF2810: Funksjonell Programmering. Strømmer

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

INF2810: Funksjonell Programmering. Oppsummering og eksamensforberedelser

UNIVERSITETET I OSLO

INF2810: Funksjonell Programmering. Lister og høyereordens prosedyrer

Innlevering 2a i INF2810, vår 2017

(define (naer-nok-kuberot? y x) (< (abs (- (kube y) x)) 0.001)) (define (naermere-kuberot y x) (/ (+ (* y 2) (/ x (kvadrat y))) 3))

Innlevering 2b i INF2810, vår 2017

INF2810: Funksjonell Programmering. Eksamensforberedelser

INF2810: Funksjonell Programmering. Muterbare data

INF2810: Funksjonell Programmering. Mer om strømmer

INF2810: Funksjonell Programmering. Mer om verditilordning. Tabeller. Og strømmer.

Forelesning 29: Kompleksitetsteori

MAT1030 Diskret Matematikk

INF2810: Funksjonell Programmering. Mer om verditilordning. Tabeller. Og strømmer.

MAT1030 Diskret Matematikk

INF2810: Funksjonell Programmering. Huffman-koding

Plenumsregning 1. Kapittel 1. Roger Antonsen januar Velkommen til plenumsregning for MAT1030. Repetisjon: Algoritmer og pseudokode

Repetisjon Novice Videregående Python PDF

MAT1030 Plenumsregning 1

INF2810: Funksjonell Programmering. Huffman-koding

Notat 2, ST Sammensatte uttrykk. 27. januar 2006

Programmering i R. 6. mars 2004

Plenumsregning 1. MAT1030 Diskret Matematikk. Repetisjon: Algoritmer og pseudokode. Velkommen til plenumsregning for MAT1030

INF3140 Modeller for parallellitet INF3140/4140: Programanalyse

Rekursiv programmering

MAT1030 Diskret matematikk

Metoder med parametre, løkker og arrayer

INF2810: Funksjonell Programmering. Huffman-koding

Memoisering, utsatt evaluering og strømmer

Løkker og arrayer. Løse problemer med programmering. INF1000, uke3 Geir Kjetil Sandve

I denne oppgaven skal vi repetere litt Python-syntaks, det er en god blanding av alle tingene du har lært i Python til nå.

"behrozm" Oppsummering - programskisse for traversering av en graf (dybde først) Forelesning i INF februar 2009

INF2810: Funksjonell Programmering. Huffman-koding

Lisp 2: Lister og funksjoner

INF2810: Funksjonell Programmering. Køer, tabeller, og (litt om) parallelitet

Transkript:

INF2810: Funksjonell programmering: Mer om Scheme. Rekursjon og iterasjon. Stephan Oepen & Erik Velldal Universitetet i Oslo 25. januar, 2013

På blokka 2 Forrige uke Introduksjon og oversikt Funksjonell programmering Litt om Scheme I dag Mer om Scheme Mer om egen-definerte prosedyrer Substitusjonsmodellen Blokkstruktur Predikater og kondisjonale uttrykk Rekursive prosedyrer Rekursive og iterative prosesser

Oppsummering: Scheme på én slide 3 Prosedyrekall i Scheme = liste. Første listeelement = prosedyren. Eksempler? (+ 1 2 10 7 5) 25 Resten = argumentene. Først evalueres alle argumentene, så kalles prosedyren på verdien av disse. Eneste unntak fra regelen er noen få såkalte special forms. For å binde et symbol til en verdi (f.eks en prosedyre) brukes define.? (* 10 (+ 60 40)) 1000? (define bar 420)? (define (foo x) (/ x 10))? (foo bar) 42

Predikater og kondisjonale uttrykk 4 Eksempler? (define foo 42)? (even? foo) #t? (zero? foo) #f? (>= foo 42) #t? (and (< 3 4) (not (zero? 42))) #t? (or (>= foo 10) (negative? foo)) #t Predikater er prosedyrer eller uttrykk som returnerer sant / usant (#t / #f). F.eks zero?, even?, >, =, etc. Husk at alt unntatt #f teller som sant. Med logiske predikater som and, or og not kan vi bygge sammensatte predikater. Sammen med kondisjonale uttrykk som f.eks if og cond kan vi bruke predikater til å kontrollere flyten i et program.

Predikater og kondisjonale uttrykk (forst.) 5 Eksempler? (define foo 42) Generell form? (define foo 42)? (if (number? foo) "number" "something else") "number"? (cond ((< foo 3)) "less") ((> foo 3) "greater") (else "equal")) "greater" (if predikat utfall-hvis-sant utfall-hvis-usant ) (cond ( predikat 1 utfall 1 ) ( predikat 2 utfall 2 ) ( predikat n utfall n ) (else utfall ))

Predikater og kondisjonale uttrykk (forst.) 6 Eksempler på special forms, ikke vanlige prosedyrer. Prosedyrer: først evalueres alle argumentene, så anvendes prosedyren. Special forms: styrer selv om og når argumentene evalueres. if og cond; det er kun uttrykkene som angir utfallet for den første testen som slår til som evalueres. or og and; evaluerer uttrykkene én av gangen fra venstre til høyre: or: hvis et uttrykk evalueres til en sann verdi returneres denne, ingen flere uttrykk evalueres. and: hvis et uttrykk evalueres til en usann verdi returneres denne, ingen flere uttrykk evalueres. Ellers returneres verdien av det siste uttrykket.

Vi skal se nærmere på prosedyre-definisjoner 7 Ting vi skal snakke mer om: Substitusjonsmodellen. Variabler, verdibinding og rekkevidde (scope). Formelle parametere og frie og bundne variabler i prosedyrekroppen. Sammensatte funksjoner. Blokkstruktur. Rekursjon og iterasjon. Prosedyrer vs. prosesser.

Substitusjonsmodellen 8 Egendefinerte prosedyrer evalueres på samme måte som beskrevet tidligere. Vi kan tenkte oss at funksjonsuttrykk skrives om på følgende måte: Hele uttrykket erstattes av prosedyrekroppen i definisjonen. Argumentnavnene i definisjonen erstattes av verdiene til uttrykkene i argumentposisjon. Dette kalles substitusjonsmodellen. (Men dette er kun en modell; senere skal vi se på en annen modell som gir et mer presist bilde.) Eksempel? (define (snitt x y) (/ (+ x y) 2)))? (snitt (snitt 10 20) (snitt 10 30)) (snitt (/ (+ 10 20) 2) (snitt 10 30)) (snitt 15 (snitt 10 30)) (snitt 15 (/ (+ 10 30) 2)) (snitt 15 20) (/ (+ 15 20) 2) 17.5

Formelle parametere, og frie / bundne variabler 9 (define (snitt x y) (/ (+ x y) 2)) Så lenge vi er konsekvente spiller det ingen rolle hvilke navn vi bruker på de formelle parameterene (x og y). Men vi bør ikke bruke navn som allerede er bundet til f.eks prosedyrer. Vi vil da overskygge prosedyrebindingen, og få problemer dersom vi ønsker å kalle prosedyren. Mer generelt kan vi snakke om to typer variabler: frie og bundne. Parameterene er eksempler på bundne variabler. Rekkevidden (scope) for bindingen er kroppen til prosedyren. I kroppen til snitt kan symbolene / og + sees som frie variabler. Deres bindinger er etablert utenfor denne leksikalske konteksten.

Sammensatte prosedyrer 10 Eksempel? (define (avslag p sum) (* (/ sum 100) p))? (avslag 25 600) 150? (define (salgspris p pris) (- pris (avslag p pris)))? (salgspris 25 600) 450 Viktig perspektiv: Å definere prosedyrer kan sees som en form for abstraksjon. Mange fordeler. I salgspris bryr vi oss ikke om hvordan avslag er implementert, så lenge den gir oss forventet returverdi. Ønsker vi å endre måten vi regner ut avslag på holder det å gjøre det i definisjonen; alle andre steder vi måtte bruke avslag kan stå uendret. Kan teste avslag isolert. Kortere kode gjennom gjenbruk.

Interne definisjoner og blokkuttrykk 11 Men la oss nå tenke oss at vi ikke ønsker å bruke avslag i andre prosedyrer enn salgspris. Vi kan da definere prosedyren avslag lokalt og internt i definisjonen til salgspris; vi sier at de danner en blokk:? (define (salgspris p pris) (define (avslag) (* (/ pris 100) p)) (- pris (avslag)))? (salgspris 25 600) 450 Her har vi samtidig forenklet avslag slik at den er helt uten parametere. Variablene pris og p er nå frie i definisjonen av avslag: Tar sin verdi fra bindingene i omsluttende definisjon av salgspris. Et eksempel på det som kalles lexical scoping.

Føler du at det er noe som mangler? 12 Koding involverer gjerne en eller annen form for løkke. I imperative/prosedyrale språk uttrykkes dette med konstruksjoner som while, for, do, until, repeat, osv. Iterativ oppdatering av lokale tilstandsvariabler. I fravær av muterbare variabler uttrykkes løkker på en annen måte i funksjonell programmering: Rekursive prosedyrekall. Løkker realisert som funksjonelle transformasjoner, ikke basert på side-effekter.

Rekursjon En rekursiv prosedyre anvender seg selv i sin egen definisjon. Klassisk eksempel: fakultetsfunksjonen. Kan se sirkulært ut, men er veldefinert så lenge vi har et eller flere basistilfeller hvor vi terminerer rekursjonen. Til sammenlikning: ikke-funksjonell kode uten rekursjon, med whileløkke og endring av lokale tilstandsvariabler. n! = { 1 hvis n = 0 n (n 1)! hvis n > 0 (define (fac n) (if (= n 0) 1 (* n (fac (- n 1))))) def fac(x): f = 1 while (x > 0): f = f * x x = x - 1 return f 13

Rekursjon (forts.) 14 Rekursjon er mest naturlig når løsningen til et problem baseres på løsningene til reduserte instanser av samme problem. Vi reduserer mot basistilfellet (base case) Nøyaktig hva vi mener med å redusere kommer an på problemet vi jobber med.? (define (fac n) (if (= n 0) 1 (* n (fac (- n 1)))))? (fac 2) 2? (fac 3) 6? (fac 4) 24? (fac 5) 120

Prosedyre vs prosess 15 Hver gang fac kaller seg selv har vi også et kall på * som venter på returverdien. Vi får en kjede av ventende kall som fortsetter å vokse helt til vi når basistilfellet, n = 0. Genererer en rekursiv prosess. (define (fac n) (if (= n 0) 1 (* n (fac (- n 1))))) Jo dypere rekursjon jo mer stakkminne brukes for å utføre prosessen. Prosessens tids- og plassbehov vokser direkte proporsjonalt med n: Eksempel på en lineær rekursiv prosess. Vekstrate (order of growth) = O(n).

Rekursiv prosess 16 Vi kan bruke substitusjonsmodellen til omskrive et kall på fac for å visualisere hvordan den rekursive prosessen utvikler seg:? (fac 7) (* 7 (fac 6)) (* 7 (* 6 (fac 5))) (* 7 (* 6 (* 5 (fac 4)))) (* 7 (* 6 (* 5 (* 4 (fac 3))))) (* 7 (* 6 (* 5 (* 4 (* 3 (fac 2)))))) (* 7 (* 6 (* 5 (* 4 (* 3 (* 2 (fac 1))))))) (* 7 (* 6 (* 5 (* 4 (* 3 (* 2 1)))))) (* 7 (* 6 (* 5 (* 4 (* 3 2))))) (* 7 (* 6 (* 5 (* 4 6)))) (* 7 (* 6 (* 5 24))) (* 7 (* 6 120)) (* 7 720) 5040

fac, versjon 2 17 Vi kan også utrykke n! med en annen prosedyre som genererer en annen type prosess. Her gjør vi rekursjonen i en internt definert hjelpeprosedyre. (define (fac n) (define (fac-iter prod count) (if (> count n) prod (fac-iter (* count prod) (+ count 1)))) (fac-iter 1 1)) I stedet for å etterlate ventende prosedyrekall legger vi til en akkumulator-variabel som akkumulerer produktet fortløpende... og en teller-variabel for å definere basistilfellet. Når vi når basistilfellet er den endelige returverdien ferdig beregnet.

fac, versjon 2 (forts.) 18 Prosedyren er fortsatt rekursiv. Men den beskriver en iterativ prosess. Prosessen vokser ikke; mengden informasjon som må huskes er konstant. (define (fac n) (define (fac-iter prod count) (if (> count n) prod (fac-iter (* count prod) (+ count 1)))) (fac-iter 1 1)) Kan til enhver tid oppsummeres med et gitt antall tilstandsvariabler; i dette tilfellet akkumulator- og teller-variablene og input. Antall trinn som utføres i fac vokser fortsatt lineært med n (O(n)). Men plassbehovet er konstant (O(1)).

Iterativ prosess 19 Igjen omskriver vi prosedyrekallene for å visualisere hvordan den komputasjonelle prosessen utvikler seg:? (fac 7) (fac-iter 1 1) (fac-iter 1 2) (fac-iter 2 3) (fac-iter 6 4) (fac-iter 24 5) (fac-iter 120 6) (fac-iter 720 7) (fac-iter 5040 8) 5040

n! som rekursiv og iterativ prosess 20 Rekursiv prosess? (define (fac n) (if (= n 0) 1 (* n (fac (- n 1))))) Iterativ prosess (define (fac n) (define (fac-iter prod count) (if (> count n) prod (fac-iter (* count prod) (+ count 1)))) (fac-iter 1 1))? (fac 7) (* 7 (fac 6)) (* 7 (* 6 (fac 5))) (* 7 (* 6 (* 5 (fac 4)))) (* 7 (* 6 (* 5 (* 4 (fac 3))))) (* 7 (* 6 (* 5 (* 4 (* 3 (fac 2)))))) (* 7 (* 6 (* 5 (* 4 (* 3 (* 2 (fac 1))))))) (* 7 (* 6 (* 5 (* 4 (* 3 (* 2 1)))))) (* 7 (* 6 (* 5 (* 4 (* 3 2))))) (* 7 (* 6 (* 5 (* 4 6)))) (* 7 (* 6 (* 5 24))) (* 7 (* 6 120)) (* 7 720) 5040? (fac 7) (fac-iter 1 1) (fac-iter 1 2) (fac-iter 2 3) (fac-iter 6 4) (fac-iter 24 5) (fac-iter 120 6) (fac-iter 720 7) (fac-iter 5040 8) 5040

fac, versjon 2 (forts.) 21 Legg også merke til at det rekursive kallet står i tail position (det siste som utføres i prosedyren). Kan sende returverdien direkte fra det innerste kallet til stedet for det ytterste kallet: tail call elimination. (define (fac n) (define (fac-iter prod count) (if (> count n) prod (fac-iter (* count prod) (+ count 1)))) (fac-iter 1 1)) Prosedyrens returverdi er den samme som returverdien for det siste rekursive kallet; den er halerekursiv (tail recursive). Alle Scheme-implementasjoner forutsettes å kunne oppdage når det foreligger halerekursjon og foreta tail call elimination. PS: SICP reserverer termen tail recursive til denne siste egenskapen som egentlig ligger hos interpreteren/kompilatoren, og beskriver ikke prosedyrer i seg selv om halerekursive.

Fibonaccitall 22 For å illustrere nok en annen type rekursiv prosess skal vi se på en prosedyre for å beregne tall i den såkalte Fibonacci-rekken: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144... Bortsett fra de to første er hvert tall gitt som summen av de to foregående. Før vi når basistilfellene fører hvert kall på fib til to nye rekursive kall. Gir opphav til en såkalt tre-rekursiv prosess. 0 hvis n = 0 fib(n) = 1 hvis n = 1 fib(n 1) + fib(n 2) ellers? (define (fib n) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2))))))? (fib 5) 5? (fib 6) 8? (fib 7) 13? (fib 8) 21? (fib 9) 34

Tre-rekursiv prosess 23 Veldig mange dupliserte operasjoner. Eksponensiell vekst i tidsbruk; proporsjonal med antall noder. Minnebruken vokser likevel lineært i n, proporsjonal med tredybden (fordi hver operasjon kun trenger å huske det som er ovenfor i treet). (fib 5) (fib 4) (fib 3) (fib 2) (fib 3) (fib 2) (fib 1) (fib 1) (fib 0) 1 (fib 1) (fib 0) (fib 2) (fib 1) 1 0 1 0 (fib 1) (fib 0) 1 1 0

fib, versjon 2 24 Vår første versjon av fib er håpløst lite effektiv. Men det betyr ikke at tre-rekursive prosesser ikke har sin plass: Når vi senere skal jobbe med hierarkiske data vil de være både elegante og nyttige! (define (fib n) (define (fib-iter a b count) (if (= count 0) b (fib-iter (+ a b) a (- count 1)))) (fib-iter 1 0 n)) Men fib kan vi omskrive til en mer effektiv halerekursiv variant. Vi bruker to akkumulator-variabler, initialisert til fib(1) og fib(0), som fortløpende oppdateres. En teller-variabel forteller når vi er ferdig. Beskriver en iterativ prosess som er lineær i n.