我试图使用关联列表在Scheme中记住factorial函数。但是,我无法让备忘录过程正常工作。我已经通过
初始化了全局空关联列表(define al '()) ; association list
接下来,我在Scheme中定义了阶乘函数:
(define (fac n)
(cond ((<= n 1) 1)
(else (* n (fac (- n 1))))))
然后我把记忆过程分成了三个部分;绑定函数,查找函数和因子存储函数。
lookup
函数检查关联列表是否已包含特定阶乘操作的值。代码如下:
(define (lookup k al) ;; lookup function
(cond
((null? al) #f)
((equal? k (caar al)) (cadr (car al)))
(else (lookup k (cdr al)))))
如果因子函数的值不在关联列表中,bind
函数将启动:
(define (bind k v al) ;; bind function
(cond
((null? al) (set! al (list (list k v))))
(else (set! al (cons (list k v) al)))) v)
最后,lookup
函数中使用了bind
和fac_mem
函数:
(define (fac_mem n) ;; fac_mem function
(cond
((equal? (lookup n al) #f) (set! al (bind n (fac n) al)))
(else (begin
(display "memoization hit \n")
(lookup n al)))))
上述目的是让fac_mem
函数取一个值n
,检查n
的阶乘是否已存在于关联列表中{{1} (通过先前计算)通过al
函数,如果关联列表中不存在,则计算lookup
的阶乘,并通过{{1}将其插入关联列表中功能。如果关联列表中存在n
的阶乘,则程序将仅通过关联列表返回值,而不是再次计算它。
但是,每当我运行bind
时,程序似乎都会提示第二个参数,就好像函数是curry一样。我不确定为什么这不起作用。
答案 0 :(得分:2)
我为了不同的目的写了这个。你可能会发现它很有用。原始版本(附加注释)可用here。
让我们考虑一个简单的程序来计算 n th 斐波纳契数。在数学上,定义是 n th 斐波那契数是 n -1 th 斐波那契数的总和和 n -2 th 斐波纳契数,前两个斐波纳契数是1和1.这直接转化为这样的方案:
(define (fib n)
(if (< n 2) 1
(+ (fib (- n 1)) (fib (- n 2)))))
计算时间显示随着 n 变大,它会迅速变慢,这是有道理的,因为算法需要指数时间来重复重新计算所有较小的斐波那契数:
> (time (fib 40))
(time (fib 40))
no collections
15069 ms elapsed cpu time
15064 ms elapsed real time
0 bytes allocated
165580141
15秒是很长一段时间来计算一个小数字。
很容易编写一个Scheme宏来记忆或缓存斐波纳契计算中固有的子问题的结果。这是宏:
(define-syntax define-memoized
(syntax-rules ()
((define-memoized (f arg ...) body ...)
(define f
(let ((cache (list)))
(lambda (arg ...)
(cond ((assoc `(,arg ...) cache) => cdr)
(else (let ((val (begin body ...)))
(set! cache (cons (cons `(,arg ...) val) cache))
val)))))))))
我们马上解释一下。但首先让我们看看如何使用该宏编写斐波纳契函数:
(define-memoized (fib n)
(if (< n 2) 1
(+ (fib (- n 1)) (fib (- n 2)))))
当然,这与早期的fibonacci函数完全相同,只是我们使用define-memoized
而不是简单的define
来编写函数。但请看一下备忘录的不同之处:
> (time (fib 40))
(time (fib 40))
no collections
0 ms elapsed cpu time
0 ms elapsed real time
5456 bytes allocated
165580141
我们已经从没有做任何工作从十五秒变为零,这是惊人的!即使计算像(fib 4000)这样的数字也不会造成任何创伤:
> (time (fib 4000))
(time (fib 4000))
no collections
141 ms elapsed cpu time
144 ms elapsed real time
1364296 bytes allocated
64574884490948173531376949015369595644413900640151342708407577598177210359034088
91444947780728724174376074152378381889749922700974218315248201906276355079874370
42751068564702163075936230573885067767672020696704775060888952943005092911660239
47866841763853953813982281703936665369922709095308006821399524780721049955829191
40702994362208777929645917401261014865952038117045259114133194933608057714170864
57836066360819419152173551158109939739457834939838445927496726613615480616157565
95818944317619922097369917676974058206341892088144549337974422952140132621568340
70101627342272782776272615306630309305298205175744474242803310752241946621965578
04131017595052316172225782924860810023912187851892996757577669202694023487336446
62725774717740924068828300186439425921761082545463164628807702653752619616157324
434040342057336683279284098590801501
它是如何工作的?高级解释是宏修改fib
以在缓存内部存储具有相同参数的先前函数调用的结果,并直接返回它们而不是重新计算它们。因此,当(fib 40)
需要(fib 39)
和(fib 38)
的结果时,结果已经可用,无需重新计算。 缓存数据结构在Scheme中称为a-list(关联列表),这意味着它是键/值对的链接列表,其中键是 n 值为(fib n)
。函数assoc
在缓存中查找键,`(,arg ...)
是一个准引号,它扩展了函数的参数(fib
只接受一个参数,但是宏允许多个函数占用多个)。 =>
符号是将cond
谓词的结果传递给其结果的语法,cdr
是从键/值对中提取值的函数。 else
的{{1}}子句计算cond
表达式中前所未见的值,然后使用let
更新缓存并返回新计算的值。
答案 1 :(得分:1)
你没有说你的程序“似乎”以什么方式提示争论,所以我无法解决这个问题(我什么也没得到)。
我会解决其他一些问题。
Scheme标准没有指定set!
是否返回有意义的值,所以如果你依赖它就会发生奇怪的事情。
首先,请注意(list (list k v))
与(cons (list k v) '())
相同,因此bind
的两个分支都做同样的事情。
我个人更喜欢成对而不是列表,因为每个键只有一个值。
一个主要问题是bind
修改了其参数al
,该变量与全局al
不是同一个变量。
这意味着您永远不会在表格中找到任何内容。
> (bind 12 "hello" al)
"hello"
> (lookup 12)
#f
> al
'()
(在开始将每个函数放在一起之前,先测试每个函数是一个非常好的主意。)
您需要set!
全局:
(define (bind k v)
(set! al (cons (cons k v) al))
v)
您可以更改lookup
以便在您使用全局时使用全局:
(define (lookup k)
(cond
((null? al) #f)
((equal? k (car (car al))) (cdr (car al)))
(else (lookup k (cdr al)))))
> (bind 12 "hello")
"hello"
> (lookup 12)
"hello"
> al
'((12 . "hello"))
在fac_mem
中,bind
已经更新了表格,因此您无需再次执行此操作。
使用let
来避免重复查找,fac_mem
变为:
(define (fac_mem n)
(let ((memo (lookup n)))
(if memo
memo
(bind n (fac n)))))
甚至
(define (fac_mem n)
(or (lookup n)
(bind n (fac n))))
(or
返回第一个不是#f
的参数。)
现在你有另一个问题:
> (fac_mem 5)
120
> al
'((5 . 120))
备忘录实际上并没有太多记忆。
将此左侧固定为练习。