Memoisering, utsatt evaluering og strømmer Først litt repetisjon: Utsatt evaluering Gitt (define (p x) (if test (x) something-else)) la E være et Scheme-uttrykk, og la L = (lambda () E). Da vil, ved kallet (p L), L bli evaluert uten forbehold, mens E bare blir evaluert hvis test #t. Spesialformene if, cond, and og or evalueres etter normal orden, som i praksis vil si at "argumentene" til disse formene gjøres til gjenstand for utsatt evaluering. 499
Memoisering: Tabellisering: Vi pakker inn en prosedyre p i en prosedyre q som har samme aritet som p og (ariteten til p er det antall argumenter p tar) en resultattabell i sin lokale omgivelse. la A være et sett med aktuelle argumenter til q. Ved første kall (q A) vil ikke tabellen ha noe entry med A som nøkkel, og dermed utføres kallet(p A), og resultatet lagres i tabellen med A som oppslagsnøkkel, før det returneres. Ved alle etterfølgende kall (q A) finner vi det tidligere beregnede resultatet i tabellen ved oppslag på A, og vi returnerer dette. 500
Hvis ariteten til p = 0 bruker vi en enkelt variabel r for resultatet, (vi bruker ikke en 0-dimensjonal tabell) men siden r alltid vil være der, og verdien #f kunne være et mulig resultat, trenger vi i tillegg et flagg som heises når resultatet beregnes. Utsatt evaluering vha. lambda Her består argumentet til q, her kalt memo-proc, i et argumentløst lambda-uttrykk med det aktuelle uttrykket, f.eks. kallet på p, som kropp. (define (memo-proc lambda-wrapped-expression) (let ((already-run? #f) ; begge disse verdiene gjelder (result #f)) ; bare frem til første kall (lambda () (if already-run? result (begin (set! already-run? #t) (set! result (lambda-wrapped-expression))); utfør det innpakede uttrykket result))) 501
Utsatt evaluering vha. spesialformen delay. Her skjer noe tilsvarende som beskrevet over, bortsett fra at spesialformen delay tar imot selve uttrykket, f.eks. et kall på p, uten lambda-innpakning. Vi kaller returverdien fra delay et løfte, og det som loves er en evaluering av uttrykket, om det skulle kreves. Vi kunne ha definert vår egen delay, slik: (define-syntax delay (syntax-rules () ((delay expression) (memo-proc ; se under (lambda () expression))))) Men merk at løfte er noe annet enn en prosedyre, hvilket vil si at returverdiene fra den forhåndsdefinerte delay og vår egen versjon av delay er av ulike typer. For å få innfridd et løfte, må vi bruke prosedyren force. Vi kan ikke ganske enkelt kalle det. 502
Strømmer cons-stream er en spesialform definert vha. delay. (define-syntax cons-stream (syntax-rules () ((cons-stream obj stream) (cons obj (delay stream))))) cons-stream tar to argumenter x og y og returnerer paret med ferdig evaluert x i car-delen og et løfte om evalueringen av y i cdr-delen. Parets car-del aksesseres ved selektoren car, men mht. strømabstraksjonen, gir vi selektoren et eget navn: stream-car. Parets cdr-del aksesseres ved løfteavkrevingsprosedyren force, men mht. strømabstraksjonen, gir vi selektoren et eget navn: stream-cdr. 503
Strømmen av heltall (define (int-stream n) (cons-stream n (int-stream (+ n 1)))) (define integers (int-stream 1)) <- n promises of n + 1 1 <- 1 2 3 4... 1 2 <- 2 3 4 5... 1 2 3 <- 3 4 5 6... 1 2 3 4 <- 4 5 6 7... 1 2 3 4 5 <- 5 6 7 8... 1 2 3 4 5 6 <- 6 7 8 9... 1 2 3 4 5 6 7 <- 7 8 9 10... Strømmen av primtall (define (prime-stream S) (if (prime? (stream-car S) (cons-stream (stream-car S)) (prime-stream (stream-cdr S)) (prime-stream (stream-cdr S)))) (define primes (prime-stream integers) <- prime? promises of integers 1 2 3 4... 2 <- 2 3 4 5... 2 3 <- 3 4 5 6... 4 5 6 7... 2 3 5 <- 5 6 7 8... 6 7 8 9... 2 3 5 7 7 8 9 10... 504
Strømmen av kvadrerte primtall (define (square-stream S) (cons-stream (square (stream-car S)) (square-stream (stream-cdr S)) (square-stream primes) <- square promises of primes 4 <- 2 3 5 7... 4 9 <- 3 5 7 11... 4 9 25 <- 5 7 11 13... 4 9 25 49 <- 7 11 13 17... 4 9 25 49 121 <- 11 13 17 19... 4 9 25 49 121 169 <- 13 17 19 23... 4 9 25 49 121 169 289 <- 17 19 23 29... 505
(square-stream (prime-stream (int-stream 1))) <- square <- prime? <- int promises of n + 1 1 <- 1 2 3 4... 4 <- 2 <- 2 <- 2 3 4 5... 4 9 <- 3 <- 3 <- 3 4 5 6... 4 <- 4 5 6 7... 4 9 25 <- 5 <- 5 <- 5 6 7 8... 6 <- 6 7 8 9... 4 9 25 49 <- 7 <- 7 <- 7 8 9 10... 8 <- 8 9 10 11... 9 <- 9 10 11 12... 10 <- 10 11 12 13... 4 9 25 49 121 <- 11 <- 11 <- 11 12 13 14... 12 <- 12 13 14 15... 4 9 25 49 121 169 <- 13 <- 13 <- 13 14 15 16... 14 <- 14 15 16 17... 15 <- 15 16 17 18... 16 <- 16 17 18 19... 4 9 25 49 121 169 289 <- 17 <- 17 <- 17 18 19 20... 506
Typiske listeoperasjoner overført på strømmer Til dataabstraksjonen for strømmer hører: (define stream-nil '()) (stream-null? stream) ; null?. Returnerer true hvis strømmen er tom. (cons-stream første-element resten) (stream-car strøm) ; returnerer første element umiddelbart (stream-cdr strøm) ; fremtvinger produksjonen av neste element fra strømmen Ved hjelp av disse kan vi definer strøm-utgaver av de fleste standardoperasjoner for lister. (define (stream-ref S n) (if (= n 0) (stream-car S) (stream-ref (stream-cdr S) ( - n 1)))) (define (stream-map proc S) (if (stream-null? S) stream-nil (cons-stream (proc (stream-car S)) (stream-map proc (stream-cdr S))))) 507
(define (stream-filter pred S) (cond ((stream-null? S) stream-nil) ((pred (stream-car S)) (cons-stream (stream-car S) (stream-filter pred (stream-cdr S)))) (else (stream-filter pred (stream-cdr S))))) stream-map og stream-filter skal selv produserer strømmer, enten den tomme strømmen eller et strøm-par (med direkte tilgjengelig car-del og et løfte i cdr-delen) enten ved bruk av cons-stream, eller ved kall på en strømgenererende prosedyre. Dette gjelder også lignende prosedyrer som vi definerer selv. Her skal alle konsekventer og alternativer i en prosedyre med flere mulige utfall, angitt ved if-, cond-, and- og or-uttrykk, returnere strømmer (nokså selvfølgelig, men allikevel mulig å overse). 508
Følgende prosedyre produserer ingen strøm, men en serie av hendelser: (define (stream-for-each proc S n) (if (or (stream-null? S) (zero? n)) 'done (begin (proc (stream-car S)) (stream-for-each proc (stream-cdr S) (- n 1))))) For å kontrollere at vi har satt opp den ene eller den andre strømmen riktig, kan det være greit å ha en rutine som denne. (define (stream->list S n) (if (or (stream-null? S) (zero? n)) '() (cons (stream-car S) (stream->list (stream-cdr S) (- n 1))))) NB! Langt de fleste av de strømmene vi skal se på, er uendelige, noe som vil gitt en evig løkke dersom stream->for-each og stream->list ikke hadde hatt en teller (her n). 509
Her er enda et par nyttige rutiner: (ikke i læreboka) list->stream gjør, som navnet tilsier, det motsatte av stream->list. (define (list->stream L) (if (null? L) stream-nil (cons-stream (car L) (list->stream (cdr L))))) stream-tail gjør nesten det samme som stream-ref, bortsette fra at den returnerer første par, snarere enn første verdi, i en gitt avstand fra begynnelsen av strømmen, (define (stream->tail S n) (if (= n 0) S (stream-tail (stream-cdr S) (- n 1)))) og i og med at returnerer et par, returnerer den også resten av lista halen fra og med n'te par. 510
Uendelige strømmer (egentlige er dette det generelle, mens endelige strømmer er, som sådanne, spesielle) Enumererte tall (define (integers-from n) (cons-stream n (integers-from (+ n 1)))) (define integers (integers-from 1)) Fibonacci-tall Fibonaccifunksjonen (define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) Fibonaccistrømmen (define (fibgen this next) (cons-stream this (fibgen next (+ this next)))) (define fibs (fibgen 0 1)) (stream->list fibs 10) (0 1 1 2 3 5 8 13 21 34) 511
Når fibonaccifunksjonen implementeres i en prosedyre, må vi sjekke for basistilfellet, og i den iterative varianten må vi, for å få med basistilfellet, hele tiden beregne leddet etter det vi skal returnere. (define (fib-iter this prev i) (if (= i 0) prev (fib-iter (+ this prev) this (- i 1)))) (map (lambda (n) (fib-iter 1 0 n)) (0 1 2 3 4 5)) 0 1 1 2 3 5 utregnet 3 + 5 prev this I strøm-varianten trenger vi ikke å sjekke forbasistilfellet, eller mer presist: vi starter med basistilfellet, i første kall på strømgeneratoren, og deretter utvikles strømmen idet vi aksesserer dens enkelte ledd, uten sjekk for noe termineringskriterium. (define (fibgen this next) (cons-stream this (fibgen next (+ this next)))) (stream->list (fibgen 0 1) 5) 0 1 1 2 3 5 utsatt 3 + 5 this next 512
Tallrekker med ut-filterte faktorer La oss se på en strøm som filtrerer ut fra heltallsstrømmen alle tall som inneholder en gitt faktor. Vi trenger da et delelighetspredikat. (define (divisible? x y) (= (remainder x y) 0)) For å se hvordan det tar seg ut, lager vi først en spesifikk strøm med alle tall som ikke har 7 som faktor. (define no-sevens (stream-filter (lambda (x) (not (divisible? x 7))) integers)) (inf-stream->list no-sevens 19) (1 2 3 4 5 6 8 9 10 11 12 13 15 16 17 18 19 20 22) Her er 7, 14 og 21 ikke med. 513
Så generaliserer vi dette til en heltallsstrøm, der vi lar faktoren være en variabel. (define (no-factor-f f) (stream-filter (lambda (x) (not (divisible? x f))) integers)) (define no-sevens (no-factor-f 7)) Vi kan generalisere ytterligere slik at generatoren, i stedet for den spesifikke strømmen integers, også tar matestrømmen som argument. (define (no-factor-f f int-stream) (stream-filter (lambda (x) (not (divisible? x f))) int-stream)) (define no-sevens (no-factor-f 7 integers)) (define no-threes-or-fives (no-factor-f 5 (no-factor-f 3 integers))) (stream->list no-threes-or-fives 10) (1 2 4 7 8 11 13 14 16 17) 514
Om vi nå sier at ingen tall i strømmen skal ha som faktor noe tidligere tall fra strømmen får vi følgende prosedyre (define (unique-factors S) (cons-stream (stream-car S) ; Vi har nå brukt faktoren (stream-car S) og ønsker ikke å se mer til den, (unique-factors (no-factor-f (stream-car S) ; så vi fjerner den fra (stream-cdr S)))))) ; den etterfølgende strømmen Det prinsippet vi her har implementert, er kjent som Eratosthenes' sil. - Om vi, gitt heltallsstrømmmen fra og med 2, - først filtrerer bort alle tall etter 2 som er delelig med 2 og - deretter filtrerer bort alle tall etter det første gjenværende som er delelig med dette, osv, så står vi igjen med strømmen av de tall som ikke er delelig med noen av sine forgjengere altså primtallene. 515
Lærebokas versjon ser slik ut: (define (sieve S) (cons-stream (stream-car S) ; Første gjenværende, her kalt p (sieve (stream-filter ; Filtrer ut (lambda (x) ; de tall som har (not (divisible? x (stream-car S)))) ; p som faktor (stream-cdr S))))) ; fra de gjenværende tallene etter p. (define primes (sieve (stream-cdr integers))) 516
Implisitte strømmer Forskjellen mellom implisitte og eksplisitte strømmer kan illustreres ved følgende: (define (gjenta) (cons-stream 'jada (gjenta))) ; eksplisitt (define repetér (cons-stream 'jada repetér)) ; implisitt Disse oppfører seg helt likt, bortsett fra at den første kalles, mens den andre bare nevnes. (car (gjenta)) jada (car (stream-cdr (gjenta))) jada (car (stream-cdr (stream-cdr (gjenta)))) jada (stream->list (gjenta) 3) (jada jada jada) (car repetér) jada (car (stream-cdr repetér)) jada (car (stream-cdr (stream-cdr repetér))) jada (stream->list repetér 3) (jada jada jada) 517
Parvis summering av to strømmer Vi bruker den eksplisitte formen når vi ønsker noe mer enn rene gjentagelser, men det betyr ikke at implisitte strømmer er trivielle. Her er en tilsynelatende triviell implisitt strøm: (define enere (cons-stream 1 enere)) ; produserer 1 1 1 1... Denne kan vi kombinere med følgende eksplisitte strøm til å gi oss enumereringen av heltallene: (define heltall (cons-stream 1 (add-streams enere heltall)) når add-streams er definert slik: Eks: (define (add-streams s1 s2) (stream-map + s1 s2)) (add-streams enere enere) ; produserer 2 2 2 2... 518
Strømmen heltall er definert over som - tallet 1 fulgt av - de parvise summene av 1 og neste heltall. (define heltall (cons-stream 1 (add-streams enere heltall)) For å skjønne hva som skjer, ser vi på realiseringen av strømmen og dens addender: enere 1 1 1 1 1 1 1 1 + + + + + + + + heltall 1 2 3 4 5 6 7 8 heltall 1 2 3 4 5 6 7 8 9 Når add-streams kalles første gang med heltall som argument, er første ledd i heltall allerede produsert. Ved andre gangs kall på add-streams, er andre ledd i heltall produsert, osv. 519
Dette inspirere til en ny måte å definere fibonacci-tallene på (define fibs (cons-stream 0 (cons-stream 1 (add-streams fibs (stream-cdr fibs))))) Her er mønstret litt mer komplisert enn for heltallstrømmen. - Vi starter vi med to kjente tall i stedet for ett, og - i stedet for å legge sammen parvis enere og suksessive heltall, så legger vi sammen parvis løpende og neste fibonaccitall. fibs 0 0 1 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 + + + + + + + + + + (stream-cdr fibs) 1 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 55 A fibs 0 0 1 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 55 A 89 C Når add-streams kalles første gang med fibs som første og (stream-cdr fibs) som andre argument, er både første og andre ledd i fibs allerede produsert. Ved andre gangs kall på add-streams, er tredje ledd i fibs produsert, osv. 520
Skalering av strømmer Følgende strøm tar en tallstrøm som input og skalerer denne ved å multiplisere hvert element i med en gitt faktor. (define (scale stream stream factor) (stream map (lambda (x) (* factor x)) stream)) (scale stream heltall 2) 2 4 6 8... (define double (cons stream 1 (scale stream double 2))) double 1 2 4 8 16 32 64... Vi kan visualisere hva som foregår i strømmen double slik: 2 2 2 2 2 2 2...... 1 2 4 8 16 32 64... 1 2 4 8 16 32 64 128... 521
522
Primtallene som en implisitt strøm et alternativ til Eratosthenes sil Vi bruker heltallsstrømmen som basis hele veien og filtrere vekk de tallene som ikke er primtall fra denne ved hjelp av den primtallsstrømmen vi genererer. Vi lar da det første primtallet 2 være car element i utgangspunktet og bruker heltallene fra 3 som inputstrøm for genereringen av resten. (define primes (cons stream 2 (stream filter prime? (heltall fra 3)))) Poenget her er at testen i prime? utføres i forhold til den selvsamme strømmen vi genererer, og ikke er basert på en ekstern metode à la Miller Rabin testen. (define (prime? n) (define (iter ps) (cond ((> (square (car ps)) n)) ; primtall ((= (remainder n (car ps)) 0) #f) ; ikke primtall (else (iter (stream-cdr ps))))) ; kanskje primtall (iter primes)) 523
Det tallet som filtreres bort i iterasjonen i prime?, hentes selv fra primtallsstrømmen, men hvordan kan en strøm som bare produserer primtall, gi fra seg et ikke-primtall for filtrering? Poenget er at primes er basert på heltallsstrømmen, slik at det tallet som iter henter fra primes er neste heltall, før det er filtrert ut, men dette kommer ikke videre til den som aksesserer primes, med mindre det slipper gjennom testen i primes?. 524
For å forstå poenget med første ledd i cond-setningen i iter, kan vi se på en alternativ utforming (define (prime? n) (define (iter ps) (cond ((> (car ps) (/ n (car ps)))) ; primtall ((= (remainder n (car ps)) 0) #f) ; ikke primtall (else (iter (stream-cdr ps))))) ; kanskje primtall (iter primes)) Testen (> (car ps) (/ n (car ps))) er i realiteten den samme som (> (square (car ps)) n)) Gitt to heltall a og b, hvis a 2 > b så er a > b/a, og omvendt. Betydningen av testen er da som følger: Hvis a er et primtall og a < b/a, så finnes det kanskje et primtall c > a, slik at c b/c, men hvis a < b/a, så kan det ikke finnes noen slik c. 525
157, 163 og 173 er alle primtall, men hva med tallene i mellom (vi sjekker bare oddetallene)? 3 159, 7 161, 3 165, (x y betyr "x deler y" "y er delelig med x") ingen av 2, 3, 5, 7, 11 deler 167, og 167 / 13 < 13, og dermed er det ingen tall større enn 13 som deler 167, ergo er 167 et primtall. Deretter ser vi at 13 169 og 3 171, ergo er 167 det eneste primtallet mellom 163 og 173. 526
Utnyttelser av strøm paradigmet Iterasjon i imperative og funksjonelle språk C, C++, Java,... Scheme int sigma(int n) (define (sigma n) { int S = 0; (define (loop i S) for (int i = 1; i <= n; i++) (if (<= i n) S += i; (loop (+ i 1) (+ S i)) return S; S)) } (loop 1 0)) int fib(int n) (define (fib n) { int a = 1, b = 0; (define (loop i a b) for (int i = 1; i <= n; i++) if (<= i n) { int f = a + b; (loop (+ i 1) b = a; (+ a b) a = f; a) } b)) return b; (loop 1 1 0)) } 527
Med strømmer kan vi gjøre det slik (define sigmas (cons-stream 0 (add-streams integers sigmas))) (define fibs (cons-stream 0 (cons-stream 1 (add-streams (stream-cdr fibs) fibs)))) Vi tar med add-streams for å vise at det ikke ligger noen tellere bak kulissene (define (add-streams S1 S2) (cons-stream (+ (stream-car S1) (stream-car S2)) (add-streams (stream-cdr S1) (stream-cdr S2)))) 528
Strømmen skiller seg vesentlig fra de andre implementasjonene ved at den, ikke har noe basistilfelle. Det er alltid ett ledd til. Dette kommer tydelig frem i kvadratrotstilnærmingen i SICP 1.1.7 (noe komprimert her). (define (square-root x) (define (loop y) (if (>= (abs (- (* y y) x)) 0.001) ; Er vi ennå ikke nær nok? (loop (/ (+ (/ x y) y) 2)) ; så looper vi videre. y)) ; Vi er nær nok og returnerer siste gjetning. (loop 1.0)) Strømversjonen har ingen test for om vi har kommet nær nok. (define (sqrt-stream x) (define guess-stream (cons-stream 1.0 (stream-map (lambda (y) (/ (+ (/ x y) y) 2)) guess-stream))) guess-stream) 529
Det finnes tilstandsbaserte sekvenser der enkel iterasjon, uansett paradigme (imperativt eller funksjonelt), gir en lite hensiktsmessig løsning, fordi vi ønsker både å ha lokal kontroll over de relevante variablene og å holde tilstandsinformasjonen skjult, samtidig som vi vil at output fra sekvensen skal være globalt tilgjengelig. Men fremfor alt ønsker vi å unngå at tilstandsvariabler sendes frem og tilbake mellom oppdateringsprosedyrer og brukerprosedyrer. Stjerneeksemplet er en randomgenerator, der løpende random-nummer er en funksjon av foregående. 530
Random-sekvenser er strømmer En random-generator er en sekvens x 0, x 1, x 2, med en tilhørende oppdateringsfunksjon f, slik at x n = f(x n 1 ). I et strengt funksjonelt program må vi sende f og x n 1 rundt omkring, fra den ene prosedyren til den andre. Dette er lite heldig, så vi bryter vi med det funksjonelle paradigme, og lager et prosedyreobjekt med x som intern tilstandsvariabel. En randomsekvens har imidlertid et vesentlig trekk felles med en strøm, idet den ikke har noe basistilfelle. Har vi først har startet en randomgenerator, er det alltid et tall til å hente. 531
I en random-strøm kjenner hvert ledd sin forgjenger, på samme måte som en rendomgenerator til enhver tid kjenner sitt sist genererte tall. La rand-init være startverdien i en randomsekvens, og la prosedyren rand-update som produsere de etterfølgende tallene i sekvensen. Vi kan da definere en randomgenerator som et prosedyreobjekt med lokal tilstand slik (define rand (let ((x rand-init)) (lambda () (set! x (rand-update x)) x))) og vi kan definere den tilsvarende randomstrømmen slik (define rand-stream (cons-stream rand-init (stream-map rand-update rand-stream))) 532
Randomgeneratoren og randomstrømmen virker på samme måte i den forstand at rand har forrige genererte random-tall x i sin omgivelse, og rand-stream får tak i forrige genererte random-tall ved å aksessere seg selv. R ==> 2 17 87 9 25 36 21 79 map rand-update R ==> 17 87 9 25 36 21 79 2 17 87 9 25 36 21 79... ri (ru 2) (ru 17) (ru 87) (ru 9) (ru 25) (ru 36) (ru 21)... Men rand-stream er ikke mer tilstandsbasert enn en hvilken som helst annen strøm som utvikles ved rekursiv gjenbruk av seg selv. Eks: (define enere (cons-stream 1 enere)) (define heltall (cons-stream 1 (add-streams enere heltall))) enere 1 1 1 1 1 1 1 1 + + + + + + + + heltall 1 2 3 4 5 6 7 8 heltall 1 2 3 4 5 6 7 8 9 Som alle andre funksjoner gir rand-update alltid samme resultat med samme argument. 533
534
En strøm av monte-carlo-verdier (se SICP 3.1.2 og forelesning 8) Vi kan implementere monte-carlo-simulering som en strøm som tar en strøm av eksperimenter (tester) som argument. (define (monte-carlo test-stream passed failed) (define (next passed failed) (cons-stream (/ passed (+ passed failed)) (monte-carlo (stream-cdr test-stream) passed failed))) (if (stream-car test-stream) (next (+ passed 1) failed) (next passed (+ failed 1)))) Den argumentløse testprosedyren experiment, som brukes i prosedyreimplementasjonen, og som ved gjentatte kall gir en boolesk sekvens, er i strømimplementasjonen erstattet med strømmen test-stream, som gir samme sekvens, hvis det initielle randomtallet er det samme. 535
Til forskjell fra monte-carlo-implementasjonene i kapittel 3.2, gir hvert ledd i strømmen et simuleringsresultat, dvs. gitt en monte-carlo-strøm, har vi for hvert ledd k resultatet av k eksperimenter, mens vi i kapittel 3.2 starter med et gitt antall eksperimenter og ikke har noe simuleringsresultat før alle eksperimentene er utført. Med samme initielle randomtall vil par nummer 1000 i randomstrømmen ha samme verdi, som det randomgeneratoren returnerer fra kall nummer 1000. Men, siden antall forsøk ikke er kjent for strømmen på forhånd, må strømmen selv telle opp alle forsøk noe den gjør ved å telle både vellykkede og mislykkede forsøk. 536
Cesaro-testen går som kjent ut på at sannsynligheten for at to vilkårlige tall a og b ikke har noen felles primtallsfaktorer = 6/ 2. For å produsere eksperimentstrømmen til monte-carlo-simuleringen definere vi en strøm som henter to og to elementer fra en annen gitt strøm og anvender en gitt binær funksjon på disse: (define (map-successive-pairs f S) (cons-stream (f (stream-car S) (stream-car (stream-cdr S))) ; dette og neste (map-successive-pairs f (stream-cdr (stream-cdr S))))) ; to videre Kaller vi denne med cesaro-testen og randomstrømmen som argumenter, får vi i retur den eksperimentstrømmen vi trenger for at monte-carlo skal gi oss en tilnærming til. 537
(define cesaro-stream (map-successive-pairs (lambda (r1 r2) (= (gcd r1 r2) 1)) rand-stream)) Eksempel: pairs (7 8 3 6 18 5 7 1 25 15 10 23 11 3 12 3 44 26 17 20 18 21 6 27... ) (map-successive-pairs (lambda (x y) (> (gcd x y) 1) pairs) ===> test-stream (#t #f #t #t #f #t #t #f #f #t #f #t... ) n-passed 1 1 2 3 3 4 5 5 5 6 6 7... ) n-failed 0 1 1 1 2 2 2 3 4 4 5 5... ) n-tests 1 2 3 4 5 6 7 8 9 10 11 12... ) n-passed 1 0.5 0.66. 0.75 0.6 0.66 0.7 0.625 0.55.. 0.6 0.54.. 0.58..... ) n-tests Det vi helst skal frem til er 6/ 2 0.6079 hvilket tar langt mer enn 12 forsøk. 538