Functional programming 在方案中创建关联列表以备备忘
我试图使用关联列表来记忆Scheme中的阶乘函数。然而,我很难让记忆过程正常工作。我已通过初始化全局空关联列表Functional programming 在方案中创建关联列表以备备忘,functional-programming,scheme,racket,Functional Programming,Scheme,Racket,我试图使用关联列表来记忆Scheme中的阶乘函数。然而,我很难让记忆过程正常工作。我已通过初始化全局空关联列表 (define al '()) ; association list 接下来,我在Scheme中定义了阶乘函数: (define (fac n) (cond ((<= n 1) 1) (else (* n (fac (- n 1)))))) 当阶乘函数的值不在关联列表中时,bind函数将启动: (define (bind k v al) ;; bind function
(define al '()) ; association list
接下来,我在Scheme中定义了阶乘函数:
(define (fac n)
(cond ((<= n 1) 1)
(else (* n (fac (- n 1))))))
当阶乘函数的值不在关联列表中时,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)
最后,在fac_mem
函数中使用了lookup
和bind
函数:
(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
,通过lookup
函数检查n
的阶乘是否已经存在于关联列表al
(之前计算得出)中,如果它不存在于关联列表中,计算n
的阶乘,并通过bind
函数将其插入关联列表。如果关联列表中确实存在n
的阶乘,则程序将通过关联列表返回值,而不是再次计算
但是,每当我运行
fac_mem
时,程序似乎会提示输入第二个参数,就好像函数是curry。我不知道为什么这不起作用。你没有说你的程序“似乎”以什么方式提示一个参数,所以我无法解决这个问题(我什么也得不到)。我将讨论其他一些问题 方案标准没有指定是否设置
代码>是否返回有意义的值,因此如果依赖它,可能会发生奇怪的事情
首先,请注意(list(listkv))
与(cons(listkv)())
相同,因此绑定的两个分支做相同的事情
我个人更喜欢成对表而不是列表,因为每个键只有一个值
一个主要问题是bind
修改其参数al
,该变量与全局al
变量不同
这意味着你永远不会在表中找到任何东西
> (bind 12 "hello" al)
"hello"
> (lookup 12)
#f
> al
'()
(在开始组装之前,单独测试每个函数是一个非常好的主意。)
您需要设置代码>全球:
(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))))
(或
返回不是#f
的第一个参数)
现在你有另一个问题:
> (fac_mem 5)
120
> al
'((5 . 120))
事实上,回忆录并没有太多回忆录
将此问题作为练习解决。您没有说明程序“似乎”以何种方式提示参数,因此我无法解决这个问题(我什么也得不到)。
我将讨论其他一些问题
方案标准没有指定是否设置代码>是否返回有意义的值,因此如果依赖它,可能会发生奇怪的事情
首先,请注意(list(listkv))
与(cons(listkv)())
相同,因此绑定的两个分支做相同的事情
我个人更喜欢成对表而不是列表,因为每个键只有一个值
一个主要问题是bind
修改其参数al
,该变量与全局al
变量不同
这意味着你永远不会在表中找到任何东西
> (bind 12 "hello" al)
"hello"
> (lookup 12)
#f
> al
'()
(在开始组装之前,单独测试每个函数是一个非常好的主意。)
您需要设置代码>全球:
(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))))
(或
返回不是#f
的第一个参数)
现在你有另一个问题:
> (fac_mem 5)
120
> al
'((5 . 120))
事实上,回忆录并没有太多回忆录
把这个问题作为练习来解决。我写这篇文章是为了另一个目的。你可能会发现它很有用。原文附有附加评注,可供查阅
我们考虑一个简单的程序来计算第n个斐波那契数。从数学上讲,第n个斐波那契数是第n-1个斐波那契数和第n-2个斐波那契数的和,前两个斐波那契数分别为1和1。这直接转化为如下方案:
(define (fib n)
(if (< n 2) 1
(+ (fib (- n 1)) (fib (- n 2)))))
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)))))
我们没有做任何工作就从15秒变为零,这是令人惊讶的!即使计算(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列表(关联列表),这意味着它是键/值对的链接列表,其中键为n,值为(fib n)
。函数assoc
在缓存中查找键,`(,arg。。