0%

Learning Lisp for CMPT 02-Advanced Functional Programming in LISP

http://www2.cs.sfu.ca/CourseCentral/310/pwfong/Lisp/2/tutorial2.html

辅助函数和累加器变量

reverse 是 Common Lisp 的内建函数,作用是将一个列表(List)反转:

USER(1): (reverse '(1 2 3 4))
(4 3 2 1)
USER(2): (reverse '(1 (a b) (c d) 4))
(4 (c d) (a b) 1)
USER(3): (reverse nil)
NIL

我们也可以很容易地实现自己的 reverse 函数,但是要提高效率并非易事,参考上一节的范例,我们可以实现一个简单的 reverse 函数:

(defun my-reverse (L)
(if (null L)
nil
(my-append (my-reverse (cdr L)) (cons (car L) nil))))

注意到上面的递归调用到了上一节的 my-append 函数来实现。但是这样调用效率是低下并且很耗费内存的。

首先我们来追踪外层的 my-reverse 函数:

CL-USER> (trace my-reverse)
(MY-REVERSE)
CL-USER> (my-reverse '(1 2 3 4))
0: (MY-REVERSE (1 2 3 4))
1: (MY-REVERSE (2 3 4))
2: (MY-REVERSE (3 4))
3: (MY-REVERSE (4))
4: (MY-REVERSE NIL)
4: MY-REVERSE returned NIL
3: MY-REVERSE returned (4)
2: MY-REVERSE returned (4 3)
1: MY-REVERSE returned (4 3 2)
0: MY-REVERSE returned (4 3 2 1)
(4 3 2 1)

然后追踪里层的 my-append 函数:

CL-USER> (trace my-append)
(MY-APPEND)
CL-USER> (my-reverse '(1 2 3 4))
0: (MY-REVERSE (1 2 3 4))
1: (MY-REVERSE (2 3 4))
2: (MY-REVERSE (3 4))
3: (MY-REVERSE (4))
4: (MY-REVERSE NIL)
4: MY-REVERSE returned NIL
4: (MY-APPEND NIL (4))
4: MY-APPEND returned (4)
3: MY-REVERSE returned (4)
3: (MY-APPEND (4) (3))
4: (MY-APPEND NIL (3))
4: MY-APPEND returned (3)
3: MY-APPEND returned (4 3)
2: MY-REVERSE returned (4 3)
2: (MY-APPEND (4 3) (2))
3: (MY-APPEND (3) (2))
4: (MY-APPEND NIL (2))
4: MY-APPEND returned (2)
3: MY-APPEND returned (3 2)
2: MY-APPEND returned (4 3 2)
1: MY-REVERSE returned (4 3 2)
1: (MY-APPEND (4 3 2) (1))
2: (MY-APPEND (3 2) (1))
3: (MY-APPEND (2) (1))
4: (MY-APPEND NIL (1))
4: MY-APPEND returned (1)
3: MY-APPEND returned (2 1)
2: MY-APPEND returned (3 2 1)
1: MY-APPEND returned (4 3 2 1)
0: MY-REVERSE returned (4 3 2 1)
(4 3 2 1)

可以看到相当的繁琐,我们要通过辅助函数和累加器变量构建效率更高的版本:

(defun my-reverse2-aux (L A)
(if (null L)
A
(my-reverse2-aux (cdr L) (cons (car L) A))))

(defun my-reverse2 (L)
(my-reverse2-aux L nil))

阶乘

我们用辅助函数的形式重写阶乘函数:

(defun my-factorial2-aux (N A)
(if (= N 1)
A
(my-factorial2-aux (- N 1) (* N A))))

(defun my-factorial2 (N)
(my-factorial2-aux N 1))

尾部递归

递归函数通常易于思考它是如何实现的,可是缺点是运行缓慢。下面我们来用尾部递归的方式实现三个函数:

  1. 计算累加, 1+2+…+N :
(defun fast-triangular-aux (N A)
(if (= N 1)
A
(fast-triangular-aux (- N 1) (+ N A))))
(defun fast-triangular (N)
(fast-triangular-aux N 1))
  1. 计算次方 B^E :
(defun fast-power-aux (B E A)
(if (zerop E)
A
(fast-power-aux B (- E 1) (* B A))))
(defun fast-power (B E)
(fast-power-aux B E 1))
  1. 计算列表长度:
(defun fast-list-length-aux (L A)
(if (null L)
A
(fast-list-length-aux (cdr L) (+ 1 A))))
(defun fast-list-length (L)
(fast-list-length-aux L 0))

函数作为对象

有时候,我们需要传递多次相同的对象。例如,我们定义一个函数:

(defun my-double (x)
(* 2 x))
USER(11): (my-double (my-double (my-double (my-double 5))))

80

然而,这样做很笨拙,还不能只管的表示出转换了多少次。我们来构建一个递归函数:

(defun repeat-transformation (F N X)
(if (zerop N)
X
(repeat-transformation F (- N 1) (funcall F X))))

我们来测试以下,重复4次 9 * 2

(repeat-transformation (function my-double) 4 9)

144

高阶函数

刚才的函数不仅能进行数学运行,还可以处理列表,比如:

(defun prepend-blah (L) (cons 'blah L))
(repeat-transformation (function prepend-blah) 4 nil)

(BLAH BLAH BLAH BLAH)

又或者,获取列表中的第七个元素:

(car (repeat-transformation (function cdr) 6 '(a b c d e f g h i j)))

G

Lambda表达式

(repeat-transformation #'(lambda (x) (* 3 x)) 5 2)

486

下面我们来构建一个函数 apply-func-list,这个函数有两个入参,第一个入参是带有一系列函数的列表,第二个入参为一个对象。例如:

(apply-func-list (list #'my-double #'list-length #'rest) '(1 2 3))

等价于

(my-double (list-length (rest '(1 2 3))))

具体的函数如下:

(defun apply-func-list (L X)
(if (null L)
X
(funcall (car L) (apply-func-list (cdr L) X))))

用几个例子测试以下:

(apply-func-list (list #'(lambda (N) (* 10 N)) #'fourth) '(10 20 30 40 50))

400
(apply-func-list (list #'third #'second) '((1 2) (3 4 5) (6)))

5
(apply-func-list (list #'(lambda (N) (- 10 N)) #'list-length) '(a b c d e f))

4
(apply-func-list (list #'list #'list) 'blah)

((BLAH))

迭代列表

(defun double-list-elements (L)
(if (null L)
nil
(cons (my-double (car L)) (double-list-elements (cdr L)))))

(defun reverse-list-elements (L)
(if (null L)
nil
(cons (reverse (car L)) (reverse-list-elements (cdr L)))))

可以将上面两个函数整合成一个:

(defun mapfirst (F L)
(if (null L)
nil
(cons (funcall F (car L)) (mapfirst F (cdr L)))))

运行:

(mapfirst #'my-double '(1 2 3 4 5))

(2 4 6 8 10)

(mapfirst #'reverse '((1 2 3) (a b c) (4 5 6) (d e f)))

((3 2 1) (C B A) (6 5 4) (F E D))

当然,也可以通过 lambda 传入函数:

(mapfirst #'(lambda (x) (* x x)) '(1 2 3 4 5))
(1 4 9 16 25)

(mapfirst #'butlast '((1 2 3) (a b c) (4 5 6) (d e f)))
((1 2) (A B) (4 5) (D E))

事实上,由于高阶函数太常用了,Common Lisp 已经内置了这样的函数,称为 mapcar

(mapcar #'(lambda (x) (* x x)) '(1 2 3 4 5))
(1 4 9 16 25)

(mapcar #'butlast '((1 2 3) (a b c) (4 5 6) (d e f)))
((1 2) (A B) (4 5) (D E))

寻找迭代 find-if

(find-if #'evenp '(1 2 3 4 5))
2

(find-if #'(lambda (X) (not (null X))) '(nil nil (1 2 3) (4 5)))
(1 2 3)

(find-if #'(lambda (X) (>= (list-length X) 3)) '((1 2) (3 4) (5 6 7) (a b c d)))
(5 6 7)

(find-if #'(lambda (X) (evenp (list-length X))) '((1) (2 3 4) (5 6)))
(5 6)

(find-if #'(lambda (X) (zerop (rem X 3))) '(1 2 3 4 5 6 7))
3

过滤迭代 filter

remove-if 是 Common Lisp 内置的过滤器:

(remove-if #'(lambda (X) (< (list-length X) 3)) '((1 2 3) (1 2) nil (1 2 3 4)))
((1 2 3) (1 2 3 4))

(remove-if #'(lambda (X) (zerop (rem X 2))) '(1 2 3 4 5 6 7 ))
(1 3 5 7)

返回多个值的函数

CL-USER> (floor 17 5)
3
2
CL-USER> (floor -17 5)
-4
3
CL-USER> (let ((x (floor 17 5))) x)
3
CL-USER> (multiple-value-bind (x y) (floor 17 5)
(+ x y))
5