lisp实现自动递归---SICP不确定性计算

一、递归的需求

1. 树的遍历,我们首先需要判断当前节点是否为叶子结点,如果不是叶子结点,则需要在左右子树上递归的去遍历;

2.迷宫的出口求解问题,当前位置是否为出口,如果不是,则需要在上下左右四个方法去递归搜索;

3.在微博看到一个爱因斯坦问题,如下:

1、在一条街上,有5座房子,喷了5种颜色。 
  2、每个房里住着不同国籍的人 
  3、每个人喝不同的饮料,抽不同品牌的香烟,养不同的宠物 
  问题是:谁养鱼? 

  提示: 
  1、英国人住红色房子 
  2、瑞典人养狗 
  3、丹麦人喝茶 
  4、绿色房子在白色房子左面 
  5、绿色房子主人喝咖啡 
  6、抽Pall Mall 香烟的人养鸟 
  7、黄色房子主人抽Dunhill 香烟 
  8、住在中间房子的人喝牛奶 
  9、 挪威人住第一间房 
  10、抽Blends香烟的人住在养猫的人隔壁 
  11、养马的人住抽Dunhill 香烟的人隔壁 
  12、抽Blue Master的人喝啤酒 
  13、德国人抽Prince香烟 
  14、挪威人住蓝色房子隔壁 
  15、抽Blends香烟的人有一个喝水的邻居 

实际上这也是一个递归(搜索问题,只不过看上去比较复杂而已。

二、自动递归的基础-延续

递归是如此的重要,如果我们有一种方法能够简化这类问题代码的编写,获得的收益将是巨大的。

使用延续,可以让我们的代码实现自动递归,即我们只用编写代码延续的条件,递归是自动执行。

1、延续的概念

续延是在运行中被暂停了的程序:即含有计算状态的单个函数型对象。当这个对象被求值时,就会在它上
次停下来的地方重新启动之前保存下来的计算。延续可以很方便的表示挂起的进程(类比linux中处于中断或者stop状态的进程),而在非确定计算中,延续表示搜索树中的节点。

续延可以理解成是一种广义的闭包。闭包就是一个函数加上一些指向闭包创建时可见的词法变量的指针。续延则是一个函数加上一个指向其创建时所在的整个栈的指针。

2、在drracket上实践延续(具体参考onlisp-20.1)

#lang racket
(define frozen 0)
(append '(the call/cc returned)
          (list (call-with-current-continuation
                 (lambda (cc)
                   (set! frozen cc)
                   'a))))
(define froz1 0)
(define froz2 0)
(let ((x 0))
    (call-with-current-continuation
     (lambda (cc)
       (set! froz1 cc)
       (set! froz2 cc)))
    (set! x (+ 1 x))
    x)
CC表示当前的延续,是一个带有一个参数的函数,参数是什么,就返回什么,上面代码的意思就是将当前的延续保存在frozen,froz1,froz2种,然后下次就可以调用frozen/1/2, 执行当前的延续。

3、执行的结果如下


上面的代码执行结果会返回2次,这点确实比较奇怪。

4、树的遍历

(define (dft tree)
  (cond ((null? tree) '())
        ((not (pair? tree)) (write tree))
        (else (dft (car tree))
              (dft (cdr tree)))))

(define *saved* '())
(define (dft-node tree)
  (cond ((null? tree) (restart))
        ((not (pair? tree)) tree)
        (else (call-with-current-continuation
               (lambda (cc)
                 (set! *saved*
                       (cons (lambda ()
                               (cc (dft-node (cdr tree))))
                             *saved*))
                 (dft-node (car tree)))))))
(define (restart)
  (if (null? *saved*)
      'done
      (let ((cont (car *saved*)))
        (set! *saved* (cdr *saved*))
        (cont))))
(define t1 '(a (b (d h)) (c e (f i) g)))

(define (dft2 tree)
  (set! *saved* '())
  (let ((node (dft-node tree)))
    (cond ((eq? node 'done) '())
          (else (write node)
                (restart)))))
执行结果如下:

注意,dft2居然延续的是(let((node (dft2 t1))), 这点确实非常不可思议,CC保存的是整个执行栈的环境。


三、common-lisp实现延续

(defvar *actual-cont* #'values)
(define-symbol-macro *cont* *actual-cont*)
(defmacro =lambda (parms &body body) 
  `#'(lambda (*cont* ,@parms) ,@body))

(defmacro =defun (name parms &body body)
  (let ((f (intern (concatenate 'string
                                "=" (symbol-name name)))))
    `(progn
       (defmacro ,name ,parms
         `(,',f *cont* ,,@parms))
       (defun ,f (*cont* ,@parms) ,@body))))

(defmacro =bind (parms expr &body body)
  `(let ((*cont* #'(lambda ,parms ,@body))) ,expr))
    
(defmacro =values (&rest retvals)
  `(funcall *cont* ,@retvals))

(defmacro =funcall (fn &rest args)
  `(funcall ,fn *cont* ,@args))

(defmacro =apply (fn &rest args)
  `(apply ,fn *cont* ,@args))

(defparameter *paths* nil)
(defconstant failsym '@)

(defmacro choose (&rest choices)
  (if choices
      `(progn
         ,@(mapcar #'(lambda (c)
                       `(push #'(lambda () ,c) *paths*))
                   (reverse (cdr choices)))
         ,(car choices))
      '(fail)))

(defmacro choose-bind (var choices &body body)
  `(cb #'(lambda (,var) ,@body) ,choices))

(defun cb (fn choices)
  (if choices
     (progn
       (if (cdr choices)
           (push #'(lambda () (cb fn (cdr choices)))
                 *paths*))
       (funcall fn (car choices)))
     (fail)))
 
(defun fail ()
  (if *paths*
      (funcall (pop *paths*))
      failsym))


解释

(=defun add1 (x) (=values (1+ x)))

将会被展开为

(progn (defmacro add1 (x)
‘(=add1 *cont* ,x))

(defun =add1 (*cont* x)
(=values (1+ x))))

*cont*的含义是绑定到当前的延续,=value显示了当前延续的作用,将结果作为参数,直接调用当前的延续。参 数 *cont* 告 诉 那 个 由 =defun 定 义 的 函 数 对 其 返 回 值 做 什 么

> (=defun message ()
(=values ’hello ’there))
MESSAGE
(=defun baz ()
(=bind (m n) (message)
(=values (list m n))))
BAZ
> (baz)
(HELLO THERE)
注意到 =bind 的展开式会创建一个称为 *cont* 的新变量。baz 的主体展开成:
(let ((*cont* #’(lambda (m n)
(=values (list m n)))))
(message))
然后会变成:
(let ((*cont* #’(lambda (m n)
(funcall *cont* (list m n)))))
(=message *cont*))
由于 *cont* 的新值是 =bind 表达式的代码体,所以当 message 通过函数调用 *cont* 来 “返回” 时,结果将是去求值这个代码体。尽管如此 (并且这里是关键), =bind 的主体里:

#’(lambda (m n)
(funcall *cont* (list m n)))
作为参数传递给 =baz 的 *cont* 仍然是可见的,所以当代码的主体求值到一个 =values 时,它将能够返回到最初的主调函数那里。所有闭包环环相扣:每个 *cont* 的绑定都包含了上一个 *cont* 绑定的闭包,它们串成一条锁链,锁链的尽头指向那个全局的值。





基于延续的自动递归


问题实践一

baker cooper fletcher miller smith分别住在一个五层公寓楼的不同层,baker不在顶层,cooper不在底层,fletcher不在顶层和底层,miller住在cooper的上面(不一定是相邻的层),smith和fletcher不在相邻的层,求他们各住在那一层。

;baker cooper fletcher miller smith
(=defun people-dwelling ()
  (choose-bind baker '(1 2 3 4 5)
    (choose-bind cooper '(1 2 3 4 5)
      (choose-bind fletcher '(1 2 3 4 5)
	(choose-bind miller '(1 2 3 4 5)
	  (choose-bind smith '(1 2 3 4 5)
	    (=values baker cooper fletcher miller smith)))))))

(defun distinct? (items)
  (cond ((null items) t)
	((member (car items) (cdr items)) nil)
	(t (distinct? (cdr items)))))

(=defun calculate ()
  (=bind (baker cooper fletcher miller smith)
      (people-dwelling)
    (if (and  
	 (distinct?  (list baker cooper fletcher miller smith))
	 (not (= baker 5))
	 (not (= cooper 1))
	 (not (= fletcher 5))
	 (not (= fletcher 1))
	 (> miller cooper)
	 (not (= (abs (- smith fletcher)) 1))
	 (not (= (abs (- fletcher cooper)) 1)))

	 (list (list 'baker baker) (list 'cooper cooper)
	       (list 'fletcher fletcher) (list 'miller miller)
	       (list 'smith smith))

	 (fail))))

结果如下:


爱因斯坦问题实践

(defmacro var-choose-choices (choices (&rest choosers) &rest body)
  (if (null choosers) 
       `(progn ,@body)
       `(choose-bind ,(car choosers) ,choices
	  (var-choose-choices ,choices ,(cdr choosers) ,@body))))

(=defun people-character ()
  (var-choose-choices '(1 2 3 4 5) 
		      (ep sp dp np gp)
		      (=values ep sp dp np gp)))


(=defun Einstein ()
  (=bind (eno sno dno nno gno)
      (people-character)
    (let ((houses (list eno    sno    dno    nno    gno)))
      (if (and 
	   (distinct? houses)
	   (= nno 1))
	  (=bind (ecolor scolor dcolor ncolor gcolor)
	      (people-character)
	    (let ((colors (list ecolor scolor dcolor ncolor gcolor)))
	      (if (and 
		   (distinct? colors)
		   (< (get-another-property 3 colors houses)
		      (get-another-property 2 colors houses))
		    (= (abs (- nno (get-another-property 5 colors houses)))
		       1)
		    (= ecolor 1))
		  (=bind (edrink sdrink ddrink ndrink gdrink)
		      (people-character)
		    (let  ((drinks (list edrink sdrink ddrink ndrink gdrink)))
		      (if (and 
			   (distinct? drinks)
			   (= ddrink 1) 
			   (= (get-another-property 3 colors drinks) 2)
			   (= (get-another-property 3 houses drinks) 3))
			  (=bind (esmoke ssomke dsmoke nsmoke gsmoke)
			      (people-character)
			    (let ((smokes (list esmoke ssomke dsmoke nsmoke gsmoke)))
			      (if (and 
				   (distinct? smokes)
				   (= (get-another-property 4 smokes drinks) 4)
				   (= gsmoke 5)
				   (= 1 (abs (- (get-another-property 3 smokes houses)
						(get-another-property 5 drinks houses))))
				   (= (get-another-property 4 colors smokes) 2))
				  (=bind (epat   spat   dpat   npat   gpat)
				      (people-character)
				    (let ((pats   (list epat   spat   dpat   npat   gpat)))
				      (if (and 
					   (distinct? pats) 
					   (= ecolor 1)
					   (= spat   1)
					   (= ddrink 1) 
					   (< (get-another-property 3 colors houses)
					      (get-another-property 2 colors houses))
					   (= (get-another-property 3 colors drinks) 2)
					   (= (get-another-property 1 smokes pats) 2)
					   (= (get-another-property 4 colors smokes) 2)
					   (= (get-another-property 3 houses drinks) 3)
					   (= nno 1)
					   (= 1 (abs (- (get-another-property 3 smokes houses)
							(get-another-property 3 pats   houses))))
					   (= 1 (abs (- (get-another-property 4 pats   houses)
							(get-another-property 2 smokes houses))))
					   (= (get-another-property 4 smokes drinks) 4)
					   (= gsmoke 5)
					   (= (abs (- nno (get-another-property 5 colors houses)))
					      1)
					   (= 1 (abs (- (get-another-property 3 smokes houses)
							(get-another-property 5 drinks houses)))))
					  (list houses colors drinks smokes pats)
					  (fail))))
				  (fail))))
				  (fail))))
			  (fail))))
		  (fail))))
	  (fail))
上面代码中e,s,d,n,g开头的单词分别代表英国人,瑞典人,丹麦人,挪威人,德国人
其中1、2、3、4、5分别代表

;1 2 3 4 5 represent
;house no 1 2 3 4 5
;red white green yellow blue
;tea coffee milk beer water
;pallmall dunhill blends bluemaster prince
;dog bird cat horse fish

代码的中间部分有些重复,把所有的条件列举了一遍,这个主要是避免条件的遗漏。

执行的结果是

养鱼的人是德国人

 
 

你可能感兴趣的:(函数式编程)