Functional programming 在方案中创建关联列表以备备忘

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

我试图使用关联列表来记忆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
    (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。。