CS 227 Lecture -*- Outline -*- Remember to talk about all let/letrec's in the programs. Show how to use (load "ch5.ss") to get polynomials * Symbolic Manipulation of Polynomials this section is a good example, both of the use of let and letrec, and of data abstraction. (remember the ratls?) It is also a good example of symbol processing in Scheme -- several large systems are built in LISP -- systems like Mathematica. This will also help us study machine arithmetic. ** Polynomials (review of math for terminology) --------------------- POLYNOMIALS 41 2 -25.2 x + 3.4 x + 2.3 x - 5.3 --------------------- *** Terms (parts of polynomial) or monomials ------------------ TERM VALUES 41 -25.2 x as printed by print-poly: -25.2 x^{41} 41 -25.2 x is a term (monomial) -25.2 is a coefficient (a real) 41 is the degree (natural number) ------------------ Terms of same degree can be added ------------------ OPERATIONS ON TERMS addition of terms of like degree: 3.1 x^2 + 2.2 x^2 ========= ------------------ and can multiply by a real ------------------ scalar multiplication: 3 7.0 * (-4 x ) = ------------------ Write these symbolically, use k for degree a_k and b_k for coefficients c for a real number Can multiply 2 terms (even if degrees differ) (a_j x^j)(b_k x^k) = (a_j b_k) (x^{j+k}) ------------------ multiplication by another term: -2.1 x^{41} * 3.0 x^2 ================= ------------------ have them do this. *** Polynomial values When add 2 terms of different degree, do not get a new term -- have to just symbolically record that they are added ------------------ POLYNOMIAL VALUES -2.1 x^{41} + 3.0 x^2 ======================= -2.1 x^{41} + 3.0 x^2 ------------------ Do this symbolically. Note that the plus is just a notation. Point out degree, leading term, leading coefficient. ------------------ Def: a polynomial value is either coef or coef x^{degree} + rp where degree is a natural, coef is a real number, and rp is a polynomial value. ------------------ *** polynomial objects ------------------ POLYNOMIAL OBJECTS Def: a polynomial object (type poly) is either the-zero-poly or (poly-cons degree coef rp) where degree is a natural, coef is a real, and rp is a polynomial object. ------------------- *** basic procedures ------------------- BASIC PROCEDURES the-zero-poly: poly poly-cons: (-> (natural real poly) poly) ; REQUIRES: poly's degree < the natural ; unless the poly is 0 x^0 degree: (-> (poly) integer) leading-coef: (-> (poly) real) rest-of-poly: (-> (poly) poly) ------------------ Note that the arguments of poly-cons are "backwards" Note that you get these by loading "ch5.ss" *** examples Build some polynomials with these on-line, or on the board Show how result of poly-cons is used by observers *** equations -------------- EQUATIONS FOR POLYNOMIALS assuming (degree p) < d (poly-cons (degree p) (leading-coef p) (rest-of-poly p)) = (degree the-zero-poly) = (degree (poly-cons d c p)) = (leading-coef the-zero-poly) = (leading-coef (poly-cons d c p)) = (rest-of-poly the-zero-poly) = (rest-of-poly (poly-cons d c p)) = ------------- Better to leave some of these blank. These are key to recursion on polys (remember data driven recursion?) Show what happens if violate restriction on poly-cons. ** Algebra of polynomials This is another layer of operations, like we did for rationals we'll use these to build addition, etc. NOT part of the basic operations *** Convenience Ops. Write these out on the board, or have them write... What would be the base case for polys? Test for zero (5.6) --------------- (zero-poly? the-zero-poly) ==> #t (zero-poly? (poly-cons 7 4. the-zero-poly)) ==> #f ;; Program 5.6 (define zero-poly? ; TYPE: (-> (poly) boolean) (lambda (poly) (and (zero? (degree poly)) (zero? (leading-coef poly))))) ;; What's wrong with the following? (define zero-poly? ; TYPE: (-> (poly) boolean) (lambda (poly) (and (zero? (car poly)) (zero? (cadr poly))))) --------------- have them answer in groups. make-term (5.7). Note args "backwards" as in poly-cons -------------- (print-poly (make-term 7 5.1)) =prints=> 5.1 x^7 ;; Program 5.7 (define make-term ; TYPE: (-> (natural real) poly) (lambda (deg coef) ; ENSURES: the result is coef*x^{deg} (poly-cons deg coef the-zero-poly))) YOU DO THESE (print-poly (monomial 5.1 7)) =prints=> 5.1 x^7 --------------------- have them do this in groups ;elegant solution... (define monomial ; TYPE: (-> (real natural) poly) (lambda (coef deg) (make-term deg coef))) leading-term (5.8) --------------- (print-poly (leading-term (poly-cons 7 5.1 (poly-cons 6 -3.2 the-zero-poly)))) =prints=> 5.1 x^7 ---------------- have them do this in groups ;; Program 5.8 (define leading-term ; TYPE: (-> (poly) poly) (lambda (poly) (make-term (degree poly) (leading-coef poly)))) and play with these. ** Programs for adding and multiplying polynomials Start with addition ------------------ ADDITION OF POLYNOMIALS -4 x^7 + 3.2 x^2 + 4.0 x + 5.3 x^5 + 7.1 x + 3.4 ========================================== (print-poly (p+ (poly-cons 7 -4 (poly-cons 2 3.2 (poly-cons 1 4.0 the-zero-poly))) (poly-cons 5 5.3 (poly-cons 1 7.1 (poly-cons 0 3.4 the-zero-poly))) )) =prints=> -4 x^7 + 5.3 x^5 + 3.2 x^2 + 11.1 x + 3.4 ------------------ *** Addition develop p+ using the following steps What is the type? poly, poly --> poly So we guess this is simultaneous (flat) recursion over polynomials base: If either argument is zero, can return the other, induction: want to do something with leading terms and the rest of each 2 cases: Degrees of each is same (poly-cons (degree poly1) ; = (degree poly2) (+ (leading-coef poly1) (leading-coef poly2)) (p+ (rest-of-poly poly1) (rest-of-poly poly2))) If degrees differ, then because of restrictions on poly-cons, have to figure out which is bigger. (if (> (degree poly1) (degree poly2)) (poly-cons (degree poly1) (leading-coef poly1) (p+ (rest-of-poly poly1) poly2))) But note the verbose and redundant computation of (degree poly1), better to use let. Show Figure 5.9 ------------------ (define p+ ; TYPE: (-> (poly poly) poly) (lambda (poly1 poly2) (cond ((zero-poly? poly1) poly2) ((zero-poly? poly2) poly1) (else (let ((n1 (degree poly1)) (n2 (degree poly2)) (a1 (leading-coef poly1)) (a2 (leading-coef poly2)) (rest1 (rest-of-poly poly1)) (rest2 (rest-of-poly poly2))) (cond ((> n1 n2) (poly-cons n1 a1 (p+ rest1 poly2))) ((< n1 n2) (poly-cons n2 a2 (p+ poly1 rest2))) (else (poly-cons n1 (+ a1 a2) (p+ rest1 rest2))) )))))) ------------------ *** Multiplication could do the same kind of thing for multiplication. ------------------ MULTIPLICATION 3 x^5 + 2 x^3 + 4 x + 3 * 4 x^2 + 3 x + 2 ========================================== 6 x^5 + 4 x^3 + 8 x + 6 + 9 x^6 + 6 x^4 + 12 x^2 + 9 x +12 x^7 + 8 x^5 + 16 x^3 + 12 x^2 ========================================== 12 x^7 + 9 x^6 + 14 x^5 + 6 x^4 + 20 x^3 + 24 x^2 + 17 x + 6 (print-poly (p* (poly-cons 5 3 (poly-cons 3 2 (poly-cons 1 4 (poly-cons 0 3 the-zero-poly)))) (poly-cons 2 4 (poly-cons 1 3 (poly-cons 0 2 the-zero-poly))))) =prints=> 12 x^7 + 9 x^6 + 14 x^5 + 6 x^4 + 20 x^3 + 24 x^2 + 17 x + 6y ------------------ This gives us a plan Multiply each term of one and add But have to do this recursively So want to multiply leading-term of one with the second, then add that to the product of the rest of one by the second. Call procedure t* that multiplies a term (or monomial) by a polynomial. Have to do this recursively also, by recursion on polys! defining t* and p* as separate defines ------------------- (define p* ; TYPE: (-> (poly poly) poly) (lambda (poly1 poly2) (if (zero-poly? poly1) the-zero-poly (p+ (t* (leading-term poly1) poly2) (p* poly1 (rest-of-poly p1)))))) (define t* ; TYPE: (-> (term poly) poly) (lambda (trm poly) ; ENSURES: result is the product of ; trm and poly (if (zero-poly? poly) the-zero-poly (poly-cons (+ (degree trm) (degree poly)) (* (leading-coef trm) (leading-coef poly)) (t* trm (rest-of-poly poly)))))) ------------------- Q: can you avoid passing any arguments in p* and t*? How? Q: can you avoid making t* a global name? How? What order is needed for the let/letrecs in the p* define? Explain literate programming stuff below --------------- <>= (define p* ;; program 5.10 (letrec (<>) (lambda (poly1 poly2) (letrec (<>) (p*-helper poly1))))) <>= (p*-helper ; TYPE: (-> (poly) poly) (lambda (p1) (if (zero-poly? p1) the-zero-poly (p+ (t* (leading-term p1) poly2) (p*-helper (rest-of-poly p1)))) )) <>= (t* (lambda (trm poly) (letrec ((t*-helper ; TYPE: (-> (poly) poly) (lambda (poly) (if (zero-poly? poly) the-zero-poly (poly-cons (+ (degree trm) (degree poly)) (* (leading-coef trm) (leading-coef poly)) (t*-helper (rest-of-poly poly))))))) (t*-helper poly)))) @ ------------------ See exercise 5.14 for even more improvements. *** Negation and Subtraction (omit) Making a polynomial negative, by multiplying by the term: -1 ------------------ (print-poly (negative-poly (poly-cons 3 4.7 (poly-cons 2 -0.97 the-zero-poly)))) =prints=> -4.7 x^3 + 0.97 x^2 ------------------ An elegant definition (define negative-poly ; TYPE: (-> (poly) poly) (lambda (poly) (p* (make-term 0 -1) poly))) Subtraction, negate and add Show 5.12 ------------- (print-poly (p- (poly-cons 7 -4 (poly-cons 2 3.2 (poly-cons 1 4.0 the-zero-poly))) (poly-cons 5 5.3 (poly-cons 2 3.2 (poly-cons 1 7.1 (poly-cons 0 3.4 the-zero-poly)))) )) =prints=> -4 x^7 + 5.3 x^5 + 3.2 x^2 + 11.1 x + 3.4 -------------- ;; Program 5.12 (define p- ; TYPE: (-> (poly poly) poly) (lambda (poly1 poly2) (p+ poly1 (negative-poly poly2)))) *** Evaluation of polynomials Given a value for x, want to get the value of the polynomial at that x. For example, 3 x^2 + 2 x +1 at x = 10 = 321. Note this is like decimal notation --------------- EVALUATION OF POLYNOMIALS (poly-value the-zero-poly 3.14) ==> 0.0 (poly-value (poly-cons 2 1.0 the-zero-poly) 5.0) ==> 25 (poly-value (poly-cons 2 3.0 (poly-cons 1 2.0 (poly-cons 0 1.0 the-zero-poly))) 10.0) ==> 321.0 poly-value: (-> (poly number) number) -------------- **** A slow way One way to do this is by flat recursion on the polynomials. Evaluate x to the power of the degree, multiply by the leading coefficient. Then for a_k x^k + ... + a_1 + c have to do k + (k-1) + ... + 1 + 0 multiplications if x^k is computed by (k-1) multiplications. This is O(k^2) multiplications. But note that in the process of computing x^k, we compute x^{k-1}. **** Horner's Rule ------------------ HORNER'S RULE FOR EVALUATING POLYNOMIALS 4 x^3 + 8 x^2 + 16.1 x + 3.1 = (4 x^2 + 8 x + 16.1) x + 3.1 = ((4 x + 8) x + 16.1) x + 3.1 = (((4) x + 8) x + 16.1) x + 3.1 ------------------ Note only 3 multiplications here. This seems like the minimum (and is). How to turn this into recursion? Lots of ways -- e.g., extract list of coefficients (reversed) and use recursion over list, accumulate answer. but the program in the book is clever, the idea is to form the result as a polynomial, and recurse until the degree is zero. ------------------ To evaluate at x = 10 4 x^3 + 8 x^2 + 16.1 x + 3.1 = (((4) 10) + 8) x^2 + 16.1 x + 3.1 = 48 x^2 + 16.1 x + 3.1 = (((48) 10) + 16.1) x + 3.1 = (480 + 16.1) x + 3.1 = 496.1 x + 3.1 = ((496.1) 10) + 3.1 = 4961 + 3.1 = 4964.1 ------------------ So the symbolic idea is a_k x^k + a_{k-1} x^{k-1} + ... + c = ((a_k x) + a_{k-1}) x^{k-1} + ... + c ------------------ (((4) x + 8) x + 16.1) x + 3.1 = ((4) x + 8) x^2 + 16.1 x + 3.1 ------------------ This is the main idea. The other detail in Figure 5.13 is with the case where the coefficient of the next lower term is 0. This is efficient if poly-cons and rest are efficient (i.e., linear if they are constant) The following is chopped up from figure 5.13 ---------------- <>= (define poly-value ; compare program 5.13 ; TYPE: (-> (poly number) number) (lambda (p num) ; ENSURES: result is p evaluated ; at x = num (let ((n (degree p))) (if (zero? n) (leading-coef p) <>)))) <>= (let ((rest (rest-of-poly p))) (if (< (degree rest) (sub1 n)) (poly-value (poly-cons (sub1 n) (* num (leading-coef p)) rest) num) (poly-value (poly-cons (sub1 n) (+ (* num (leading-coef p)) (leading-coef rest)) (rest-of-poly rest)) num))) @ ---------------- Q: can you avoid passing any arguments repeatedly here? How? num. By using another letrec. ** Representations of polynomials *** List of coefficients (5.14) Have them think how to write primitives -- show rest-of-poly, note does not return 0 coefficient terms except for the zero poly. -------------- ;; from figure 5.14 (define rest-of-poly ; TYPE: (-> (poly) poly) (lambda (poly) (cond ((zero? (degree poly)) the-zero-poly) ((zero? (leading-coef (cdr poly))) (rest-of-poly (cdr poly))) (else (cdr poly))))) ------------ -- note that poly-cons is not a constant time operation. *** List of degree-coefficient pairs (5.15) rest-of-poly basically cdr with error checking poly-cons basically cons with error checking so constant time. ** printing polynomials (omit) Show how to write a poly-print procedure (if time) ------------------ <>= (define print-poly ;TYPE: (-> (poly) void) (lambda (poly) ; EFFECT: print external form of poly ; e.g., 3 x^{42} + -8 x^2 + 2 x + 4.1 (letrec (<> <> <>) (begin (display-leading-term poly) (print-helper (rest-of-poly poly)))))) <>= (print-helper ; TYPE: (-> (poly) void) (lambda (p) ; EFFECT: Print + and then poly, ; not printing 0 terms (if (zero-poly? p) (newline) (begin (display " + ") (display-leading-term p) (print-helper (rest-of-poly p)))) )) <>= (display-leading-term ; TYPE: (-> (poly) void) (lambda (p) ; EFFECT: print the leading term of p (begin (display (leading-coef p)) (display " ") (display-degree (degree p))))) <>= (display-degree ; TYPE: (-> (integer) void) (lambda (n) ; REQUIRES: n >= 0 ; EFFECT: print x and degree in the ; standard format for polynomials (cond ((= n 1) (display "x")) ((= n 0) (display "")) ((and (> n 1) (< n 10)) (begin (display "x^") (display n))) (else (begin (display "x^{") (display n) (display "}")))))) @ ------------------