Exercise 2.87
A polynomial is zero if all the coefficients of all its terms are zeroes. To implement this logic we can add the following code to the polynomial package:
(define (zero-terms? L)
(if (empty-termlist? L)
#t
(and (=zero? (coeff (first-term L)))
(zero-terms? (rest-terms L)))))
(define (zero-poly? p)
(zero-terms? (term-list p)))
(put '=zero? '(polynomial) zero-poly?)
Exercise 2.88
To implement the subtraction operation for polynomials we will follow the given hint and reduce the operation to addition. In order to achieve this we have to define a generic negate operation and provide implementations of it for all the types in the arithmetic system. For all types, except polynomials, negate can be defined as multiplication by -1:
;;generic negate operation
(define (negate x) (apply-generic 'negate x))
;;add to the scheme number package
(put 'negate '(scheme-number)
(lambda (x) (tag (* -1 x))))
;;add to the rational number package
(put 'negate '(rational)
(lambda (x) (tag (mul-rat (make-rat -1 1) x))))
;;add to the complex number package
(put 'negate '(complex)
(lambda (x) (tag (mul-complex (make-from-real-imag -1.0 0.0) x))))
To negate a polynomial, however, we have to negate all the coefficients of its terms:
;;code to be added to the polynomial package
(define (negate-terms L)
(if (empty-termlist? L)
(the-empty-termlist)
(let ((t (first-term L)))
(adjoin-term (make-term (order t) (negate (coeff t)))
(negate-terms (rest-terms L))))))
(define (negate-poly p)
(make-poly (variable p)
(negate-terms (term-list p))))
(put 'negate '(polynomial) (lambda (p) (tag (negate-poly p))))
Finally, we can reduce subtraction to addition with the negative of the subtrahend as addend:
;;code to be added to the polynomial package
(define (sub-poly p1 p2)
(add-poly p1 (negate-poly p2)))
(put 'sub '(polynomial polynomial)
(lambda (p1 p2) (tag (sub-poly p1 p2))))
Exercise 2.89
The following procedures implement the term-list representation as appropriate for dense polynomials and can replace the previous representation in polynomial package:
;;Term lists for dense polynomials
(define (make-term order coeff) (list order coeff))
(define (order term) (car term))
(define (coeff term) (cadr term))
(define (the-empty-termlist) '())
(define (first-term term-list)
(make-term (- (length term-list) 1)
(car term-list)))
(define (rest-terms term-list) (cdr term-list))
(define (empty-termlist? term-list) (null? term-list))
(define (adjoin-term term term-list)
(let ((term-order (order term))
(list-length (length term-list)))
(cond ((= term-order list-length)
(cons (coeff term) term-list))
((> term-order list-length)
(cons (coeff term)
(adjoin-term (make-term (- term-order 1) 0) term-list)))
(else (error "Term order to small to be adjoined" (list term term-list))))))
The most interesting parts are the first-term and adjoin-term procedures.
Since the dense representation is a list of coefficients, first-term has to construct a new term from the coefficient found at the car of its term-list argument and compute its order by taking the length of term-list and subtracting 1.
Adjoin-list works by assuming that the order of its first argument – term (the term being adjoined) is greater than the order of its second argument – term-list. If this condition is not met an error is signaled. Otherwise the resulting term list is the coefficient of the term, followed by some number of zeroes, followed by the term-list we are adjoining to. This is done recursively. The base case is the situation where term “fits” directly in front of the term-list – no extra zeroes are required. In the recursive case we cons the coefficient of term with the result of adjoining a new term with a coefficient of zero and an order of one less than that of the current term with term-list.
Exercise 2.90
To make the system support both sparse and dense polynomial representations I have decided to split the polynomial package in two separate packages – one for sparse polynomials and one for dense polynomials, each of them containing a different representation of term-lists:
;;;Sparse polynomial package
(define (install-sparse-polynomial-package)
;;internal procedures
(define (make-poly variable term-list)
(cons variable term-list))
(define (variable p) (car p))
(define (term-list p) (cdr p))
(define (same-variable? v1 v2)
(and (symbol? v1) (symbol? v2) (eq? v1 v2)))
;;Term lists for sparse polynomials
(define (make-term order coeff) (list order coeff))
(define (order term) (car term))
(define (coeff term) (cadr term))
(define (the-empty-termlist) '())
(define (first-term term-list) (car term-list))
(define (rest-terms term-list) (cdr term-list))
(define (empty-termlist? term-list) (null? term-list))
(define (adjoin-term term term-list)
(if (=zero? (coeff term))
term-list
(cons term term-list)))
(define (zero-terms? L)
(if (empty-termlist? L)
#t
(and (=zero? (coeff (first-term L)))
(zero-terms? (rest-terms L)))))
(define (negate-terms L)
(if (empty-termlist? L)
(the-empty-termlist)
(let ((t (first-term L)))
(adjoin-term (make-term (order t) (negate (coeff t)))
(negate-terms (rest-terms L))))))
(define (add-terms L1 L2)
(cond ((empty-termlist? L1) L2)
((empty-termlist? L2) L1)
(else
(let ((t1 (first-term L1))
(t2 (first-term L2)))
(cond ((> (order t1) (order t2))
(adjoin-term t1 (add-terms (rest-terms L1) L2)))
((< (order t1) (order t2))
(adjoin-term t2 (add-terms L1 (rest-terms L2))))
(else
(adjoin-term
(make-term (order t1)
(add (coeff t1) (coeff t2)))
(add-terms (rest-terms L1) (rest-terms L2)))))))))
(define (mul-terms L1 L2)
(if (empty-termlist? L1)
(the-empty-termlist)
(add-terms (mul-term-by-all-terms (first-term L1) L2)
(mul-terms (rest-terms L1) L2))))
(define (mul-term-by-all-terms t1 L)
(if (empty-termlist? L)
(the-empty-termlist)
(let ((t2 (first-term L)))
(adjoin-term
(make-term (+ (order t1) (order t2))
(mul (coeff t1) (coeff t2)))
(mul-term-by-all-terms t1 (rest-terms L))))))
(define (add-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(make-poly (variable p1)
(add-terms (term-list p1)
(term-list p2)))
(error "Polys not in same variable -- ADD-POLY" (list p1 p2))))
(define (mul-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(make-poly (variable p1)
(mul-terms (term-list p1)
(term-list p2)))
(error "Polys not in same variable -- MUL-POLY" (list p1 p2))))
(define (sub-poly p1 p2)
(add-poly p1 (negate-poly p2)))
(define (zero-poly? p)
(zero-terms? (term-list p)))
(define (negate-poly p)
(make-poly (variable p)
(negate-terms (term-list p))))
;;interface to rest of system
(define (tag x)
(attach-tag 'sparse-polynomial x))
(put 'add '(sparse-polynomial sparse-polynomial)
(lambda (p1 p2) (tag (add-poly p1 p2))))
(put 'sub '(sparse-polynomial sparse-polynomial)
(lambda (p1 p2) (tag (sub-poly p1 p2))))
(put 'mul '(sparse-polynomial sparse-polynomial)
(lambda (p1 p2) (tag (mul-poly p1 p2))))
(put '=zero? '(sparse-polynomial) zero-poly?)
(put 'negate '(sparse-polynomial) (lambda (p) (tag (negate-poly p))))
(put 'make 'sparse-polynomial
(lambda (var terms) (tag (make-poly var terms))))
'done)
(define (make-sparse-polynomial var terms)
((get 'make 'sparse-polynomial) var terms))
;;;Dense polynomial package
(define (install-dense-polynomial-package)
;;internal procedures
(define (make-poly variable term-list)
(cons variable term-list))
(define (variable p) (car p))
(define (term-list p) (cdr p))
(define (same-variable? v1 v2)
(and (symbol? v1) (symbol? v2) (eq? v1 v2)))
;;Term lists for dense polynomials
(define (make-term order coeff) (list order coeff))
(define (order term) (car term))
(define (coeff term) (cadr term))
(define (the-empty-termlist) '())
(define (first-term term-list)
(make-term (- (length term-list) 1)
(car term-list)))
(define (rest-terms term-list) (cdr term-list))
(define (empty-termlist? term-list) (null? term-list))
(define (adjoin-term term term-list)
(let ((term-order (order term))
(list-length (length term-list)))
(cond ((= term-order list-length)
(cons (coeff term) term-list))
((> term-order list-length)
(cons (coeff term)
(adjoin-term (make-term (- term-order 1) 0) term-list)))
(else (error "Term order to small to be adjoined" (list term term-list))))))
(define (zero-terms? L)
(if (empty-termlist? L)
#t
(and (=zero? (coeff (first-term L)))
(zero-terms? (rest-terms L)))))
(define (negate-terms L)
(if (empty-termlist? L)
(the-empty-termlist)
(let ((t (first-term L)))
(adjoin-term (make-term (order t) (negate (coeff t)))
(negate-terms (rest-terms L))))))
(define (add-terms L1 L2)
(cond ((empty-termlist? L1) L2)
((empty-termlist? L2) L1)
(else
(let ((t1 (first-term L1))
(t2 (first-term L2)))
(cond ((> (order t1) (order t2))
(adjoin-term t1 (add-terms (rest-terms L1) L2)))
((< (order t1) (order t2))
(adjoin-term t2 (add-terms L1 (rest-terms L2))))
(else
(adjoin-term
(make-term (order t1)
(add (coeff t1) (coeff t2)))
(add-terms (rest-terms L1) (rest-terms L2)))))))))
(define (mul-terms L1 L2)
(if (empty-termlist? L1)
(the-empty-termlist)
(add-terms (mul-term-by-all-terms (first-term L1) L2)
(mul-terms (rest-terms L1) L2))))
(define (mul-term-by-all-terms t1 L)
(if (empty-termlist? L)
(the-empty-termlist)
(let ((t2 (first-term L)))
(adjoin-term
(make-term (+ (order t1) (order t2))
(mul (coeff t1) (coeff t2)))
(mul-term-by-all-terms t1 (rest-terms L))))))
(define (add-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(make-poly (variable p1)
(add-terms (term-list p1)
(term-list p2)))
(error "Polys not in same variable -- ADD-POLY" (list p1 p2))))
(define (mul-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(make-poly (variable p1)
(mul-terms (term-list p1)
(term-list p2)))
(error "Polys not in same variable -- MUL-POLY" (list p1 p2))))
(define (sub-poly p1 p2)
(add-poly p1 (negate-poly p2)))
(define (zero-poly? p)
(zero-terms? (term-list p)))
(define (negate-poly p)
(make-poly (variable p)
(negate-terms (term-list p))))
;;interface to rest of system
(define (tag x)
(attach-tag 'dense-polynomial x))
(put 'add '(dense-polynomial dense-polynomial)
(lambda (p1 p2) (tag (add-poly p1 p2))))
(put 'sub '(dense-polynomial dense-polynomial)
(lambda (p1 p2) (tag (sub-poly p1 p2))))
(put 'mul '(dense-polynomial dense-polynomial)
(lambda (p1 p2) (tag (mul-poly p1 p2))))
(put '=zero? '(dense-polynomial) zero-poly?)
(put 'negate '(dense-polynomial) (lambda (p) (tag (negate-poly p))))
(put 'make 'dense-polynomial
(lambda (var terms) (tag (make-poly var terms))))
'done)
(define (make-dense-polynomial var terms)
((get 'make 'dense-polynomial) var terms))
Exercise 2.91
The completed implementation of div-terms follows:
(define (div-terms L1 L2)
(if (empty-termlist? L1)
(list (the-empty-termlist) (the-empty-termlist))
(let ((t1 (first-term L1))
(t2 (first-term L2)))
(if (> (order t2) (order t1))
(list (the-empty-termlist) L1)
(let ((new-c (div (coeff t1) (coeff t2)))
(new-o (- (order t1) (order t2))))
(let ((rest-of-result
(div-terms
(sub-terms
L1
(mul-term-by-all-terms
(make-term new-o new-c)
L2))
L2)))
(list (adjoin-term
(make-term new-o new-c)
(car rest-of-result))
(cadr rest-of-result))))))))
(define (sub-terms L1 L2)
(add-terms L1 (negate-terms L2)))
Note that we had to define sub-terms as a helper procedure so that we could subtract two term lists. To do so we rely on the negate-terms procedure that was defined earlier (see Exercise 2.88).
Having this dividing two polynomials becomes straight forward:
(define (div-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(let ((result (div-terms (term-list p1) (term-list p2))))
(list (make-poly (variable p1)
(car result))
(make-poly (variable p1)
(cadr result))))
(error "Polys not in same variable -- DIV-POLY" (list p1 p2))))
Exercise 2.92
Skipped.
Exercise 2.93
After modifying the rational-number package to use generic operations and changing the constructor so that no reduction of fractions takes place, we can create rational functions and apply generic arithmetic on them:
(define p1 (make-polynomial 'x '((2 1) (0 1))))
(define p2 (make-polynomial 'x '((3 1) (0 1))))
(define rf (make-rational p2 p1))
(add rf rf)
=> (rational (polynomial x (5 2) (3 2) (2 2) (0 2)) polynomial x (4 1) (2 2) (0 1))
As can be expected the result is not reduced to lowest terms.
Exercise 2.94
Definitions of remainder-terms, gcd-terms, and gcd-poly which can be added to the polynomial package:
(define (remainder-terms a b)
(cadr (div-terms a b)))
(define (gcd-terms a b)
(if (empty-termlist? b)
a
(gcd-terms b (remainder-terms a b))))
(define (gcd-poly a b)
(if (same-variable? (variable a) (variable b))
(make-poly (variable a)
(gcd-terms (term-list a)
(term-list b)))
(error "Polys not in same variable -- GCD-POLY" (list a b))))
Code for installing in the system greatest-common-divisor as a generic operation and registering concrete implementations for ordinary numbers and polynomials:
;;Generic operation - greatest-common-divisor
(define (greatest-common-divisor x y) (apply-generic 'greatest-common-divisor x y))
;;Should be added to the polynomial package
(put 'greatest-common-divisor '(polynomial polynomial)
(lambda (p1 p2) (tag (gcd-poly p1 p2))))
;;Should be added to the scheme-number package
(put 'greatest-common-divisor '(scheme-number scheme-number)
(lambda (x y) (tag (gcd x y))))
When testing greatest-common-divisor we get:
(p1 (make-polynomial 'x '((4 1) (3 -1) (2 -2) (1 2))))
(p2 (make-polynomial 'x '((3 1) (1 -1)))))
(greatest-common-divisor p1 p2)
=> (polynomial x (2 -1) (1 1))
Exercise 2.95
Let’s try the example:
(define p1 (make-polynomial 'x '((2 1) (1 -2) (0 1))))
(define p2 (make-polynomial 'x '((2 11) (0 7))))
(define p3 (make-polynomial 'x '((1 13) (0 5))))
(define q1 (mul p1 p2))
(define q2 (mul p1 p3))
(greatest-common-divisor q1 q2)
=> (polynomial x (2 1458/169) (1 -2916/169) (0 1458/169))
Greatest-common-divisor definitely does not return p1, but it does return a polynomial (of the same order as p1) that divides both q1 and q2. So why does it not return p1? Apparently because it has found some other polynomial that it deems “greater” than p1, and that is a divisor of q1 and q2.
Exercise 2.96
When defining pseudoremainder-terms it is convenient to have a helper procedure that computes the integerizing factor when given two term lists:
(define (integerizing-factor a b)
(if (or (empty-termlist? a) (empty-termlist? b))
0
(let ((t1 (first-term a))
(t2 (first-term b)))
(expt (coeff t2)
(+ (order t1)
(- (order t2))
1)))))
(define (pseudoremainder-terms a b)
(remainder-terms
(mul-term-by-all-terms
(make-term 0 (integerizing-factor a b))
a)
b))
After modifying the gcd-terms procedure we can test again the example from the previous exercise and confirm that we do not get fractions:
(define (gcd-terms a b)
(if (empty-termlist? b)
a
(gcd-terms b (pseudoremainder-terms a b))))
(greatest-common-divisor q1 q2)
=> (polynomial x (2 1458) (1 -2916) (0 1458))
The rest of the exercise requires gcd-terms to remove the common factors from the coefficients of its result. To simplify the implementation we can define another helper procedure reduce-termlist:
(define (reduce-termlist term-list)
(mul-term-by-all-terms (make-term 0
(div 1 (apply gcd (map coeff term-list))))
term-list))
(define (gcd-terms a b)
(if (empty-termlist? b)
(reduce-termlist a)
(gcd-terms b (pseudoremainder-terms a b))))
Exercise 2.97
Definitions of reduce-terms and reduce-poly. To simplify the implementation two helper procedures – mul-terms-by-scalar and quotient-terms are introduced:
(define (mul-terms-by-scalar s term-list)
(mul-term-by-all-terms (make-term 0 s)
term-list))
(define (quotient-terms a b)
(car (div-terms a b)))
(define (reduce-terms n d)
(let* ((g (gcd-terms n d))
(factor
(integerizing-factor
(if (> (order (first-term n))
(order (first-term d)))
n
d)
g))
(n1 (mul-terms-by-scalar factor n))
(d1 (mul-terms-by-scalar factor d))
(n2 (quotient-terms n1 g))
(d2 (quotient-terms d1 g))
(coeff-gcd (gcd (apply gcd (map coeff n2))
(apply gcd (map coeff d2)))))
(list (mul-terms-by-scalar (div 1 coeff-gcd) n2)
(mul-terms-by-scalar (div 1 coeff-gcd) d2))))
(define (reduce-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(let ((result (reduce-terms (term-list p1) (term-list p2))))
(list (make-poly (variable p1) (car result))
(make-poly (variable p2) (cadr result))))
(error "Polys not in same variable -- REDUCE-POLY" (list p1 p2))))
Code that defines the generic reduce operation, provides implementations of it for polynomials and scheme-numbers and reimplements the make-rat constructor:
;;Generic operation - reduce
(define (reduce x y) (apply-generic 'reduce x y))
;;Should be added to the scheme-number package
(put 'reduce '(scheme-number scheme-number)
(lambda (x y)
(let ((result (reduce-integers x y)))
(list (tag (car result)) (tag (cadr result))))))
;;Should be added to the polynomial package
(put 'reduce '(polynomial polynomial)
(lambda (p1 p2)
(let ((result (reduce-poly p1 p2)))
(list (tag (car result)) (tag (cadr result))))))
;;Should be added to the rational package in place of the old constructor
(define (make-rat n d)
(let ((result (reduce n d)))
(cons (car result) (cadr result))))
Now if we test the code with the rational functions given at the beginning of the extended exercise we get:
(p1 (make-polynomial 'x '((1 1) (0 1))))
(p2 (make-polynomial 'x '((3 1) (0 -1))))
(p3 (make-polynomial 'x '((1 1))))
(p4 (make-polynomial 'x '((2 1) (0 -1))))
(rf1 (make-rational p1 p2))
(rf2 (make-rational p3 p4))
(add rf1 rf2)
=> (rational (polynomial x (3 -1) (2 -2) (1 -3) (0 -1)) polynomial x (4 -1) (3 -1) (1 1) (0 1))
Note that the result is almost correct, the only difference is that the numerator and denominator are both multiplied by -1, but otherwise the rational function is simplified as expected.
Read Full Post »