Programmönster: # Listan som sekvens, Rekursiv process Enkel genomgång av sekvens (element på toppnivån i en lista)) TDDC60 Programmering: abstraktion och modellering Föreläsning 5 Rekursiva och iterativa programmeringsmönster för sekvenser Rekursiva mönster för binära träd och hierarkiska strukturer. Högre ordningens mönster. Sammanfattning rekursiva modeller. Metodik för att utveckla och verifiera rekursiva funktioner. Två fall: q Tom sekvens: () q Sekvens av element: (a b c) (DEFINE(FN SEQ) (IF (NULL? SEQ) <null-resultat> (<operation> (FIRST SEQ) (FN (REST SEQ))))) (define (sum-rec seq-rec); Summera talen i en lista (sekvens) 0 (+ (first seq) (sum-rec (rest seq))))) ; (sum-rec '(2 4 6)) = 2 Programmönster: # Listan som sekvens, Rekursiv process (define (increase-rec seq n) ; Öka alla elementen i en lista (sekvens) med n (cons (+ (first seq) n) (increase-rec (rest seq))))) ; (increase-rec '(3 5 7) 00) = (03 05 07) (define (my-append seq seq2) ; Sätt samman två listor (sekvenser) (if (null? seq) seq2 (cons (first seq) (my-append (rest seq) seq2)))) ; (my-append '( 2 3) '(a b c)) = ( 2 3 a b c) Programmönster: # Listan som sekvens, Iterativ process (DEFINE (FN SEQ) (DEFINE (ITER SEQ RESULT) (IF (NULL? SEQ) RESULT (ITER (REST SEQ) (<operation> (FIRST SEQ) RESULT)))) (ITER SEQ <start-result>)) (define (sum-iter seq) ; Summera talen (define (iter seq result) result (iter (rest seq) (+ result (first seq))))) (iter seq 0)) ; (sum-iter (2 4 6)) = 2 Programmönster: # Listan som sekvens, Iterativ process Observera att resultatet byggs upp framifrån av elementen i listan. Jämför med rekursiv process där vi bygger upp bakifrån. (define (increase-iter seq n) ; summera alla talen i en lista (sekvens) (define (iter seq res) res (iter (cdr seq) (put-last (+ (first seq) n) res)))) (iter seq )) (define (put-last e seq) (append seq (list e))) ; (increase-iter '(3 5 7) 00) = (03 05 07) Programmönster: #2 Som mönster # men särskild bearbetning av enskilda element Tre fall: q Tom sekvens: () q Första elementet uppfyller vissa villkor: (2 a b c) q Generella fallet: (a b c ) (DEFINE (FN SEQ) (COND ((NULL? SEQ) <null-result>) ((<condition> (FIRST SEQ)) <result>))... fler villkor... (ELSE (<operation> (FIRST SEQ) (FN (REST SEQ))))))
Programmönster #2: Särskild bearbetning av enskilda element. Rekursiv process (define (number-in-list? seq) ; Innehåller en lista ett tal (cond ((null? seq) #f) ((number?(first seq)) #t) (else (number-in-list? (rest seq))))) ; (number-in-list? '(a 3 b)) = #t ; (number-in-list? '(a b c)) = #f (define (rem-numbers seq) ; Tag bort alla tal från en lista (sekvens) (cond ((null? seq)) ((number? (first seq)) (rem-numbers (rest seq))) (else (cons (first seq) (rem-numbers (rest seq)))))) Programmönster #2: Särskild bearbetning av enskilda element. Rekursiv process (define (substitute seq old new) ; ersätt alla av ett givet element med ett nytt element (cond ((null? seq) ) ((eq? (first seq) old) (cons new (substitute (rest seq) old new))) (else (cons (first seq) (substitute (rest seq) old new)))))) ; Ersättt alla a med x ; (substitute '(a b c a d a) 'a 'x) = (x b c x d x) ; (rem-numbers '(a 2 b c 3)) = (a b c) Programmönster: #3 Listan innehåller element som i sin tur kan vara listor. Sekvenser. Hierarkiska strukturer. Tre Fall: q Tomma: () q Första element är inte en lista (atomärt): (a (b) c) q General case: ((a b) (b c) (d)) Ger en sekvenslösning. Brukar benämnas dubbelrekursion (car-cdr rekursion) Programmönster: #3 Listor med listor (define (rem-all-numbers seq) ; Tar bort alla tal på alla nivåer (cond ((null? seq) ) ((atom? (first seq)) (if (number? (first seq)) (rem-all-numbers (rest seq)) (cons (first seq) (rem-all-numbers (rest seq))))) (else (cons (rem-all-numbers (first seq)) (rem-all-numbers (rest seq)))))) ; Ersätt alla a mot x på alla nivåer (rem-all-numbers '(a (3 4) ((a 5 d) e))) = (a () ((a d) e)) (define (atom? obj) (not (list? obj))) Binärt träd. Trädet har noder och bågar. Toppen benämns rot och avslutas i löv. Kan skapas av cons-par: (cons (cons (cons 2) (cons 'a 'b)) 'x) = (((. 2). (a. b)). x) x Programmönster: #4 Binär trädstruktur. Representerat som punkterade par. Två fall: q Löv: a q Generellt fall, nod: (((a. b). (b. c)). d) Tre fall: q Tomt träd: () q Löv: a q Generellt fall, nod: (((a. b). (b. c)). d) 2 a b 2
(DEFINE (FN TREE) (COND ; ((EMPTY? TREE) <empty-result>);finns tomt träd? ((LEAF? TREE) <leaf-result>) (ELSE (<operation> (FN (LEFT TREE)) (FN (RIGHT TREE)))))) Med representation av trädet som punkterade par definieras primitiverna: (define (leaf? bt) (not (pair? bt))) (define left car) (define right cdr) (define (leaves bt) ; returnerar antalet löv (if (leaf? bt) (+ (leaves (left bt))) (leaves (right bt))))) (leaves '(((. 2). (a. b)). x) = 5 Med abstraktion kan trädet vara representerat på andra sätt. I stälet för ett punkterat par kan representionen vara en lista med två element, höger resp. vänster delträd. Huvudfunktionen blir samma. (define (leaf? bt) (not (pair? bt?))) (define left car) (define right cadr) (define (leaves bt) ; returnerar antalet löv (if (leaf? bt) (+ (leaves (left bt))) (leaves (right bt))))) (leaves '((( 2) (a b)) x)) = 5 Här representeras ett aritmetiskt uttryck som ett binärt träd. Operatorn lagras i noden. Bearbetningen av uttrycken sker med binär-träd mönstret. Ett aritmetiskt uttryck lagras som: löv - konstant nod - (operand- operator operand-2) 3 + 4 * (5 + 6) -> representeras (3 + (4 * (5 + 6))) Selektorer för att komma åt delarna från en nod, dvs vänster resp. höger operand och operatorn. (define oper- first) (define oper-2 third) (define operator second) (define (value expr) ; beräknar värdet av ett aritmetiskt uttryck (cond ((number? expr) expr) ((eq? (operator expr) '+) (+ (value (oper- expr)) (value (oper-2 expr)))) ((eq? (operator expr) '*) (* (value (oper- expr)) (value (oper-2 expr)))))) (value '(3 + (4 * (5 + 6)))) = 47 Programmönster: #5 Generella listor. Listor kan avslutas med punkterat par. Kan innehålla punkterad lista. Listan avslutas ej med tomma listan. (cons 'a (cons 'b 'c)) = (a. (b. c)) = (a b. c) Testas med pair?. (Äkta listor kan testas med list?) CAR och CDR används på sådana listor. Fyra fall: q Tomma listan: () q Icke-lista (atom): a q Första elementet är icke-lista (atomär): (a (b c) (d). e) q Generellt fall: ((a. b) (b c) (d). e) 3
Programmönster: #5 Generella listor. (define (add-one gen-list); Öka varje tal med. (cond ((null? gen-list) ) ((atom? gen-list) (if (number? gen-list) (+ gen-list) gen-list)) ((atom? (car gen-list)) (if (number? (car gen-list)) (cons (+ (car gen-list)) (add-one (cdr gen-list))) (cons (car gen-list) (add-one (cdr gen-list))))) (else (cons (add-one (car gen-list)) (add-one (cdr gen-list)))))) Högre ordningens funktioner för listor n Alla mönster kan omformas till högre ordningens funktioner/procedurer där operationer/funktioner, startvärde mm ges som parametrar. n Speciellt för listan (sekvensen) finns ofta många fördefinierade högra ordningens funktioner. (add-one '(a (b. 2) (c (3)). 4)) = (a 2 (b. 3) (c (4)). 5) Mönster # som högre ordningens funkiton (DEFINE (FN TREE) (if (LEAF? TREE) <leaf-result> (<operation> (FN (LEFT TREE)) (FN (RIGHT TREE))))) Mönster (define (bin-tree node-fn leaf-fn tree) (if (leaf? tree?) (leaf-fn tree) Högre ordningens funktion (node-op (bin-tree node-fn leaf-fn (left tree)) (bin-tree node-fn leaf-fn (right tree))))) Mönster # som högre ordningens funkiton (define (bin-tree node-fn leaf-fn tree) (if (leaf? tree) (leaf-fn tree) (node-fn (bin-tree node-fn leaf-fn (left tree)) (bin-tree node-fn leaf-fn (right tree))))) ; Skapa ett nytt träd där vänster och höger ; deltäd byter plats på alla nivåder (bin-tree (lambda (left right) (cons right left)) (lambda (leaf) leaf) '(((. 2). (a. b)). x) = (x. ((b. a). (2. ))) = (x (b. a) 2. ) Observera att Scheme skriver ut i första hand i listformat. Högre-ordningens procedurer för sekvenser. Exempel på funktioner från kursboken (avs 2.2 I SICP) Map gör något på varje element i en sekvens (define (map proc seq) (cons (proc (first seq)) (map proc (rest seq))))) ; öka varje element med 5 (map (lambda (e)( + e 5)) '(2 6 4)) = ( 2+5 6+5 4+5 ) = (7 9) Filter - ta ut vissa element från en lista (define (filter predicate seq) (cond ((null? seq) ) ((predicate (first seq)) (cons (first seq) (filter predicate (rest seq)))) (else (filter predicate (rest seq))))) ; Ta ut alla positiva talen (filter (lambda (x) (> x 0)) '(3-5 4 2-7)) = (3 4 2) 4
Enumerate / generate - skapa successiva element i en lista (enumerate 3 0) = (3 4 5 6 7 8 9 0) Skapa en lista med nr element där nästföljande element beräknas fram av föregående. (define (my-generate next-proc start nr) (if (= nr 0) (cons start (my-generate next-proc (next-proc start) (- nr ))))) Accumulate ( kallas även Reduce) (define (accumulate op initial sequence) (if (null? sequence) initial (op (first sequence) (accumulate op initial (rest sequence))))) (accumulate + 0 '(2 4 6)) = (+ 2 (+ 4 (+ 6 0))) = 2 accumulate list '( 2 3)) = = (list (list 2 (list 3 ))) = ( (2 (3 ()))) (my-generate (lambda (n) (* n n)) 2 5) = (2 4 6 256 65536) Problem kan nu lösas genom lämplig kombination av sådana funktioner Vem har mest lön av anställda med lön under 0.000 med anställningsnummer från 50 till 00? (accumulate max 0 (filter (lambda (salary) (< salary 0000)) (map fetch-salary (enumerate 50 00)))) Antag det finns en funktion fetch-salary som givet ett anställningsnummer ger lönen. 5