我正试图在计划中记住一个程序。代码来自SICP
我的程序fib定义为
(define (fib n)
(display "computing fib of ")
(display n) (newline)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1))
(fib (- n 2))))))
我的记忆程序如下
(define (memoize f)
(let ((table (make-table)))
(lambda (x)
(let ((previously-computed-result (lookup x table)))
(or previously-computed-result
(let ((result (f x)))
(insert! x result table)
result))))))
让我们定义两个程序
(define mem-fib (memoize fib))
(define mem-fib-lambda (memoize (lambda (n)
(display "computing fib of ")
(display n)
(newline)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (memo-fib (- n 1))
(memo-fib (- n 2))))))))
如你所见,在mem-fib中,我使用fib作为参数,但在mem-fib-lambda中,我使用lambda表达式作为参数,它几乎相同。
使用5作为参数调用此过程会产生不同的结果,其中第一个mem-fib将最后一个结果存储在其表中,而mem-fib-lambda存储每个递归计算。
(mem-fib 5)
->computing fib of 5
->computing fib of 4
->computing fib of 3
->computing fib of 2
->computing fib of 1
->computing fib of 0
->computing fib of 1
->computing fib of 2
->computing fib of 1
->computing fib of 0
->computing fib of 3
->computing fib of 2
->computing fib of 1
->computing fib of 0
->computing fib of 1
->5
(mem-fib 5)
->5
和
(mem-fib-lambda 5)
->computing fib of 5
->computing fib of 4
->computing fib of 3
->computing fib of 2
->computing fib of 1
->computing fib of 0
->5
(mem-fib-lambda 5)
->5
我的理论是,当我调用mem-fib fib在另一个环境中计算时,而mem-fib-lambda正在环境中计算它被称为。
为了解决这个问题,我试图在memoization程序中复制一份
(define (memoize proc)
(define f proc) ;; Here
(let ((table (make-table)))
(lambda (x)
(let ((previously-computed-result (lookup x table)))
(or previously-computed-result
(let ((result (f x)))
(insert! x result table)
result))))))
这没用,所以我尝试将它放在let表达式中。据我所知,fib应该是与表
相同的框架的一部分(define (memoize proc)
(let ((table (make-table))
(f proc)) ;; Here
(lambda (x)
(let ((previously-computed-result (lookup x table)))
(or previously-computed-result
(let ((result (f x)))
(insert! x result table)
result))))))
这也没有做任何事情。
我错过了什么?为什么行为存在差异?我怎样才能得到我想要的结果?
谢谢
答案 0 :(得分:4)
The problem is that, in your first function, you are calling the non-memoized version of fibonacci recursively, not the memoized version of fib. A way around this would be to define fib like this:
(define (fib n)
(display "computing fib of ")
(display n) (newline)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (mem-fib (- n 1)) ;; Notice we're calling the memoized version here
(mem-fib (- n 2))))))
(define mem-fib (memoize fib))
An arguably better way would be to do the following:
(define (fib n)
(display "computing fib of ")
(display n) (newline)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1)) ;; Notice we're calling the NON-memoized version here
(fib (- n 2))))))
(set! fib (memoize fib)) ;; but we redefine fib to be memoized
This means we're only using one name and it's memoized. There is no good way to have both versions laying around, but if you want to, here is one way you would do it (if you want to compare performance or something):
(define (fib n)
(display "computing fib of ")
(display n) (newline)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (mem-fib (- n 1)) ;; Notice we're calling the memoized version here
(mem-fib (- n 2))))))
(define mem-fib (memoize fib))
(set! fib (lambda (n)
(display "computing fib of ")
(display n) (newline)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1)) ;; Notice we're calling the NON-memoized version here
(fib (- n 2)))))))
答案 1 :(得分:1)
以下是解决此问题的另一种方法。这是在Racket,而不是Scheme,我为此道歉(它可能是Scheme,除非我不知道hashtable如何在Scheme中工作)。
首先,这是一个Racket函数,用于在其第一个参数上记忆任意数量的参数的函数。很明显,为什么我们需要在一瞬间允许额外的论点。
(define (memoize f (table (make-hasheqv)))
;; Memoize a function on its first argument.
;; table, if given, should be a mutable hashtable
(λ (k . more)
;; hash-ref! looks up k in table, and if it is not there
;; sets it to be the result of calling the third argument (or
;; to the third argument if it's not callable). thunk just makes
;; a function of no arguments.
(hash-ref! table k
(thunk (apply f k more)))))
现在就是诀窍:不是将fib
定义为递归函数,而是将fib/c
定义为非 - 递归函数,它知道如何做一个 - 斐波纳契系列计算的步骤和其他功能的其他功能。它还会告诉你它的功能是做什么的。
(define (fib/c n c)
;; fib/c does one step of the fibonacci calculation,
;; calling c to do the remaining steps.
(printf "fib of ~A~%" n)
(if (<= n 2)
1
(+ (c (- n 1) c)
(c (- n 2) c))))
基于此,我们可以非常轻松地定义fib/u
,这是一个未被删除的Fibonacci函数,它具有您期望的可怕性能,只需将fib/c
本身作为其第二个参数传递:
(define (fib/u n)
;; unmemoized fib
(fib/c n fib/c))
但是现在我们可以记住它,并定义一个memoized版本,fib/m
(在这里你可以看到为什么我需要memoize
允许多个参数:我们需要继续传递记忆功能下来:
(define (fib/m n)
;; and here's a memoized fib
(let ((fib/m (memoize fib/c)))
(fib/m n fib/m)))
现在(4是他们不同的第一种情况):
> (fib/u 4)
fib of 4
fib of 3
fib of 2
fib of 1
fib of 2
3
> (fib/m 4)
fib of 4
fib of 3
fib of 2
fib of 1
3
删除打印后:
> (time (fib/u 40))
cpu time: 8025 real time: 7962 gc time: 26
102334155
> (time (fib/m 40))
cpu time: 1 real time: 1 gc time: 0
102334155
注意这种方法,你写一个非递归函数&amp;把它变成一个递归的,与Y的推导如何完成密切相关(尽管这些通常是非常纯粹的并且坚持只有一个参数的函数,所以最终得到(λ (f) (λ (n) ... (f ...) ...))
而不是{{ 1}}。事实证明这是一个非常有用的技巧。