CS 541 Lecture -*- Outline -*- * Streams and Lazy evaluation ** Goals *** capture common patterns (paradigms) once and for all *** efficiently and perspiciously express certain algorithms ** Example from Abelson and Sussman, 3.4.1 *** compute sum of squares of leaves of a tree that are odd enumerate -> filter(odd?) -> map(square) -> accumulate(+,0) --------------- (define (sum-odd-squares tree) (if (leaf-node? tree) (if (odd? tree) (square tree) 0) (+ (sum-odd-squares (left-branch tree)) (sum-odd-squares (right-branch tree))))) --------------- *** construct list of all odd Fibonacci numbers less than or equal to n enumerate -> map(fib) -> filter(odd?) -> accumulate(cons,'()) --------------- (define (odd-fibs n) (define (next k) (if (> k n) '() (let ((f (fib k))) (if (odd? f) (cons f (next (1+ k))) (next (1+ k)))))) (next 1)) --------------- want to separate out the enumeration, mapping, filtering, accumulation *** express the basic operations using functionals over streams **** enumeration of tree leaves --------------- (define (enumerate-tree tree) (if (leaf-node? tree) (cons-stream tree the-empty-stream) (append-streams (enumerate-tree (left-branch tree)) (enumerate-tree (right-branch tree))))) (define (append-streams s1 s2) (if (empty-stream? s1) s2 (cons-stream (head s1) (append-streams (tail s1) s2)))) --------------- **** enumeration of integers in an interval --------------- (define (enumerate-interval low high) (if (> low high) the-empty-stream (cons-stream low (enumerate-interval (1+ low) high)))) --------------- **** accumulation --------------- (define (accumulate combiner initial-value stream) (if (empty-stream? stream) initial-value (combiner (head stream) (accumulate combiner initial-value (tail stream))))) --------------- **** filtering --------------- (define (filter pred stream) (cond ((empty-stream? stream) the-empty-stream) ((pred (head stream)) (cons-stream (head stream) (filter pred (tail stream)))) (else (filter pred (tail stream))))) --------------- **** mapping --------------- (define (map proc stream) (if (empty-stream? stream) the-empty-stream (cons-stream (proc (head stream)) (map proc (tail stream))))) --------------- *** can now put the pieces together --------------- (define (sum-odd-squares tree) (accumulate + 0 (map square (filter odd? (enumerate-tree tree))))) (define (list-square-fibs n) (accumulate cons '() (filter odd? (map fib (enumerate-interval 1 n))))) --------------- easy to combine these in different ways: e.g., list of squares of fibionnaci numbers list of odd squares... ** Power of map, filter, accumulate paradigm R. Waters (1979), about 60% of traditional fortran programs have loops that can be viewed as instances of map, filter and accumulate *** representing standard data as streams polynomials as stream of coefficients vectors as streams of numbers matricies as streams of vectors ** Nested Mappings *** Example, enumerate pairs i,j such that j < i < n and i+j is prime **** idea enumerate 1 to n -> form pairs -> filter(for primes) form pairs by enumerating j < i, form pair: (list i j) ; the following computes stream of streams of pairs (map (lambda (i) (map (lambda (j) (list i j)) (enumerate-interval 1 (+ -1 i)))) (enumerate-interval 1 n)) **** some tools ---------------- ; flatten takes a stream of streams and combines the elements ; to form a single stream (define (flatten stream) (accumulate append-streams the-empty-stream stream)) (define (flatmap f s) (flatten (map f s))) ---------------- **** code for prim-sum-pairs ---------------- (define (prime-sum-pairs n) (map (lambda (pair) (list (car pair) (cadr pair) (+ (car pair) (cadr pair)))) (filter (lambda (pair) (prime? (+ (car pair) (cadr pair)))) (flatmap (lambda (i) (map (lambda (j) (list i j)) (enumerate-interval 1 (-1+ i)))) (enumerate-interval 1 n))))) ---------------- ** Stream comprehension like { x | x > 0} but for streams for above example, want to say form stream of all pairs (list i j) where i is element of (enumerate-interval 1 n) and j is element of (enumerate-interval (-1 + i)) such that (prime? (+ i j)) *find these parts in code above* what does this call for? *syntactic sugar* a la KRC (D. Turner) (define (prime-sum-pairs n) (collect (list i j) ((i (enumerate-interval 1 n)) (j (enumerate-interval 1 (-1+ i)))) (prime? (+ i j)))) why does this have to be a sugar? ** Lazy evaluation, computing with infinite objects *** Infinite streams **** explicit definition ---------------- (define (integers-starting-from n) (cons-stream n (integers-starting-from (1+ n)))) (define integers (integers-starting-from 1)) ---------------- **** computations with infinite streams sieve takes a stream S and returns a stream consisting of head of S and all numbers in the tail not divisible by the head ---------------- (define (divisible? x y) (= (remainder x y) 0)) (define (sieve stream) (cons-stream (head stream) (sieve (filter (lambda (x) (not (divisible? x (head stream)))) (tail stream))))) (define primes (sieve (integers-starting-from 2))) (define (nth-stream n s) (if (= n 0) (head s) (nth-stream (-1+ n) (tail s)))) (nth-stream 50 primes) ; Value 233 ---------------- **** implicit definition use lazy evaluation (define ones (cons-stream 1 ones)) (define integers (cons-stream 1 (add-streams ones integers))) (define fibs (cons-stream 0 (cons-stream 1 (add-streams (tail fis) fibs))))