Memoization in Functional Style

Published on Jun 02, 2012

函数式的记忆优化

记忆利用闭包。这种优化只对函数式有用。函数的行为只受输入参数约束,函数所做的仅仅是计算并将结果返回给调用者。

优化的地方在于:记住每次传递的参数和计算的结果。

我们的Dice of Doom游戏可以做如下优化:

记忆neighbors Function

正如你所记得的,neighbors函数重复检查board的所有边。然而board的形状比赛中从来不变化,这个函数的结果不会变化!

(let ((old-neighbors (symbol-function 'neighbors))
      (previous (make-hash-table)))
  (defun neighbors (pos)
    (or (gethash pos previous)
        (setf (gethash pos previous) (funcall old-neighbors pos)))))

首先我们把老的neighbors函数保存在局部变量old-neighbors中。symbol-function命令仅仅取得绑在这个符号上的函数。

接下来,我们定义一个局部变量previous,用来存放所有先前的参数和结果。

然后我们把先前的neighbors覆盖掉,然后?很显然,每调用一次后检查是否previous已有,若没有则调用老函数并保存参数和结果。

注意……这种函数处理行为可能搞的很乱……

记忆Game Tree

game tree的生成是整个程序最大的开销,可以这样优化。

(let ((old-game-tree (symbol-function 'game-tree))
      (previous (make-hash-table :test #'equalp)))
  (defun game-tree (&rest rest)
    (or (gethash rest previous)
       (setf (gethash rest previous) (apply old-game-tree rest)))))

因为哈希表中的对象是array,所以我们用=equalp=代替=eql=。=&rest=接受任意数量参数。

记忆rate-position函数

这是用lisp实现人工智能中minmax算法中的一个函数,我好像还没纪录人工智能那部分。 如下:

(let ((old-rate-position (symbol-function 'rate-position))
      (previous (make-hash-table)))
  (defun rate-position (tree player)
    (let ((tab (gethash player previous)))
      (unless tab
        (setf tab (setf (gethash player previous) (make-hash-table))))
      (or (gethash tree tab)
          (setf (gethash tree tab)
                (funcall old-rate-position tree player))))))

我们需要做些特殊的东西,因为传递进rate-position中的参数。游戏树将非常之大,所以我们必定不要用equal或相似的这种比较大列表相当慢的函数来比较一个游戏树,取而代之,我们用eql,因此我们分开处理函数的两个参数,通过内嵌哈希表实现。

首先创建一个用eql比较的外部哈希表,然后定义一个tab变量在外部哈希表中查找我们的其中一个参数player,获取内部hash表。如果外部没查找到,我们建一个空的内建哈希表,用同样的键保存在外部哈希表中,剩下的例子和先前一样,除了我们使用内嵌哈希表,把tree这个参数作为键值。

注意

记忆化可以优化函数式程序,然而记忆化本身一点也不函数式。所以说函数式必然效率低么?其实这种优化我倒希望编译器能自动完成。

补记

这些日子忙着准备考试,然而考试当天剩一小时写完果断交卷。对大学考试已经没任何想法了,浪费我时间而已。

land of lisp根本这几天就没看,试了试racket,相当有意思,库也挺强大。我还是喜欢只有47页规范的scheme啊,所以看完land of lisp想先看看How to Design Programms,common lisp上前页的规范真不是newbie friendly。