sicp每日一题[2.73]
最近状态不太好,再加上2.73前面的内容有点多,学的有点吃力,所以昨天就没做。。
Exercise 2.73
Section 2.3.2 described a program that performs symbolic differentiation:
(define (deriv exp var)
(cond ((number? exp) 0)
((variable? exp)
(if (same-variable? exp var) 1 0))
((sum? exp)
(make-sum (deriv (addend exp) var)
(deriv (augend exp) var)))
((product? exp)
(make-sum (make-product
(multiplier exp)
(deriv (multiplicand exp) var))
(make-product
(deriv (multiplier exp) var)
(multiplicand exp))))
⟨more rules can be added here⟩
(else (error "unknown expression type: DERIV" exp))))
We can regard this program as performing a dispatch on the type of the expression to be differentiated. In this situation the “type tag” of the datum is the algebraic operator symbol (such as +) and the operation being performed is deriv. We can transform this program into data-directed style by rewriting the basic derivative procedure as
(define (deriv exp var)
(cond ((number? exp) 0)
((variable? exp) (if (same-variable? exp var) 1 0))
(else ((get 'deriv (operator exp))
(operands exp) var))))
(define (operator exp) (car exp))
(define (operands exp) (cdr exp))
a. Explain what was done above. Why can’t we assimilate the predicates number? and variable? into the data-directed dispatch?
b. Write the procedures for derivatives of sums and products, and the auxiliary code required to install them in the table used by the program above.
c. Choose any additional differentiation rule that you like, such as the one for exponents (Exercise 2.56), and install it in this data-directed system.
d. In this simple algebraic manipulator the type of an expression is the algebraic operator that binds it together. Suppose, however, we indexed the procedures in the opposite way, so that the dispatch line in deriv looked like
((get (operator exp) 'deriv) (operands exp) var)
What corresponding changes to the derivative system are required?
首先做这道题之前要先把 put 和 get 函数给加进来
(define *operation-table* (make-hash))
(define (put op-type op-name procedure)
(hash-set! *operation-table* (list op-type op-name) procedure))
(define (get op-type op-name)
(hash-ref *operation-table* (list op-type op-name) #f))
a. 上面的程序就是从 data-directed dispatch 表里取出一个操作为 deriv,类型为 (operator exp) 的过程,并把表达式和变量传给这个提取出的过程。不把 number? 和 variable? 放到 data-directed dispatch 表的原因是因为它们两个只需要一个参数,而这个程序会给取出的过程2个参数。我看到别人的答案基本都是说这两个过程没有标签,而且不需要加标签,所以不能放入 data-directed dispatch 表,但是题目问的是 why can't,它们的解释不是 can't 而是 needn't,不知道我的理解对不对。
b&c. 这两问情况一样,难度也不大,参考 2.56 的练习,只需要修改一下取操作数的逻辑就可以了,因为 “+, *, **” 本身已经作为区分采用哪个过程的符号,所以取操作数不是从第二个取,而是第一个,其他地方就照搬过来就可以了。
(define (install-deriv-package)
;; internal procedures
;; sum
(define (make-sum a1 a2)
(cond ((=number? a1 0) a2)
((=number? a2 0) a1)
((and (number? a1) (number? a2)) (+ a1 a2))
(else (list '+ a1 a2))))
(define (sum? x) (and (pair? x) (eq? (car x) '+)))
(define (addend s) (car s))
(define (augend s) (cadr s))
(define (sum-deriv expr var)
(make-sum (deriv (addend expr) var)
(deriv (augend expr) var)))
;; product
(define (make-product m1 m2)
(cond ((or (=number? m1 0) (=number? m2 0)) 0)
((=number? m1 1) m2)
((=number? m2 1) m1)
((and (number? m1) (number? m2)) (* m1 m2))
(else (list '* m1 m2))))
(define (product? x) (and (pair? x) (eq? (car x) '*)))
(define (multiplier p) (car p))
(define (multiplicand p) (cadr p))
(define (product-deriv expr var)
(make-sum (make-product (deriv (multiplier expr) var)
(multiplicand expr))
(make-product (multiplier expr)
(deriv (multiplicand expr) var))))
;; exponentiate
(define (make-exponentiation base exponent)
(cond ((=number? base 0) 0)
((=number? base 1) 1)
((and (number? base) (=number? exponent 0)) 1)
((and (number? base) (=number? exponent 1)) base)
((and (number? base) (number? exponent)) (* base (make-exponentiation base (- exponent 1))))
(else (list '** base exponent))))
(define (base e) (car e))
(define (exponent e) (cadr e))
(define (exponentation-deriv expr var)
(make-product (exponent expr)
(make-product
(make-exponentiation (base expr)
(make-sum (exponent expr) -1))
(deriv (base expr) var))))
;; interface to the rest of the system
(put 'deriv '+ sum-deriv)
(put 'deriv '* product-deriv)
(put 'deriv '** exponentation-deriv)
'done)
(install-deriv-package)
(deriv '(+ x x x) 'x)
(deriv '(* x x x) 'x)
(deriv '(+ x (* x (+ x (+ y 2)))) 'x)
(deriv '(+ x (* 3 (+ x (+ y 2)))) 'x)
(deriv '(** x 3) 'x)
; 结果如下
'done
2
'(+ x x)
'(+ 1 (+ (+ x (+ y 2)) x))
4
'(* 3 (** x 2))
d. 这样的话,只要在使用 put 函数的时候也先传类型,再传操作就可以了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?