Common Lisp学习笔记(十一)

 

11 Iteration and Block Structure

11.2 dotimes and dolist

dotimesdolist都是macro funcexpress

(dotimes (index-var n [result-form])
  body)

(dolist (index-var list [result-form])
  body)

dotimes执行n次body,index-var从0加到n - 1. 若是参数有result-form则最后返回它的值,若是没有则返回nilapp

dolist用法相同,只是index-var改为一个list,迭代完list中的元素less

> (dotimes (i 4) (format t "count ~S~&" i))
count 1
count 2
count 3
count 4
nil

> (dolist (x '(red blue green) 'flowers)
    (format t "~&roses are ~S" x))
roses are red
roses are blue
roses are green
flowers

11.2 exiting the body of a loop return能够从loop中返回,return只有一个参数就是要返回的值函数

(defun find-first-odd (list-of-numbers)
  (dolist (e list-of-numbers)
    (format t "~&testing ~S..." e)
    (when (oddp e)
      (format t "found an odd number.")
      (return e))))

ex 11.1oop

(defun it-member (x y)
  (dolist (e y nil)
    (if (equal x e) (return t))))

ex 11.2测试

(defun it-assoc (x y)
  (dolist (e y nil)
    (if (equal x (first e)) (return e))))

ex 11.3spa

(defun check-all-odd (list-of-numbers)
  (cond ((null list-of-numbers) t)
        (t (format t "~&checking ~S" (first list-of-numbers))
           (unless (evenp (first list-of-numbers))
                (check-all-odd (rest list-of-numbers))))))

11.4 recursive, iterative

当在一个简单的list上进行i遍历的时候,使用iterative能够更加简单的遍历这个list,有几个缘由:1.dolist能够自动判断list是否结束,而递归须要使用cond写明终止条件 2.在迭代中使用e就能够访问list中的每一个元素,而递归的时候则要当心注意每次要(first list)来访问第一个元素,而后使用(rest list)来缩小范围rest

可是也有不少状况使用递归会更加方便。首先递归的方法很容易理解,将问题描述得很是清楚。如在遍历tree的结构的时候,使用递归能够很简单,可是用迭代的方法来遍历树的结构须要加很是多的条件判断和控制等。code

ex 11.4orm

(defun it-length (x)
  (let ((result 0)) 
    (dolist (e x result)
        (incf result))))

ex 11.5

(defun it-nth (n x)
  (dotimes (i n (first x))
    (pop x)))

ex 11.6

(defun it-union (x y)
  (let ((result nil))
    (dolist (e x)
        (unless (member e result) (push e result)))
    (dolist (e y result)
        (unless (member e result) (push e result)))))

(defun it-union (x y)
  (dolist (e x y)
    (unless (member e y) (push e y))))

11.6 比较dolist, mapcar, recursion

mapcar是对一个list的每一个元素都使用一个func操做的最简单的方式

好比要计算一个list的全部元素的平方

(defun app-square-list (list-of-numbers)
  (mapcar #'(lambda (n) (* n n)) list-of-numbers))

(defun rec-square-list (x) 
  (cond ((null x) nil)
        (t (cons (* (first x) (first x))
                 (rec-square-list (rest x))))))

迭代的方式虽然不像递归要注意结束条件的判断,可是要用不断使用显式赋值来获得结果

(defun it-square-list (list-of-numbers)
  (let ((result nil))
    (dolist (e list-of-numbers (reverse result))
        (push (* e e) result))))

返回的结果要加一个reverse,由于每次是把当前计算的平方push到result的末尾,因此获得的list是倒过来的

ex 11.8

(defun it-reverse (x)
  (let ((result nil))
    (dolist (e x result)
        (push e result))))

11.7 DO macro

(do ((var1 init1 [update1])
     (var2 init2 [update2])
     ...)
    (test action1 action2 actionn)
    body)

do是lisp中最强大的迭代函数,它能够:

  • 绑定任意数量的变量,像let
  • 本身定义每一步的step长度
  • 本身定义什么时候跳出循环的测试

首先,var list中的变量会初始化为init的值,而后判断test,若是为t,则对后面的全部action求值,并返回最后一个的值。若是为nil,则进入body,执行全部的body语句。在body中能够加入return指令直接结束do函数。执行完body后会回到var list中更新var的值,每一个var会更新为[update]中获得的值,若是没有该update则该var保持不变。接着继续执行test判断是进入action仍是body. do只要test为真就会进入action执行而后就会结束

eg,

(defun launch (n)
  (do ((cnt n (- cnt 1)))
      ((zerop cnt) (format t "blast off!"))
      (format t "~S..." cnt)))

ex 11.9

(defun check-all-odd (x)
  (do ((e x (rest e)))
      ((null e) t)
      (format t "~&checking ~S..." (first e))
      (if (evenp (first e)) (return nil))))

ex 11.10

(defun launch (n)
  (dotimes (i n)
    (format t "~S..." (- n i)))
  (format t "blast off"))

11.8 隐式赋值的好处

do与dotimes和dolist相比有不少好处:

  • 能够定义每一步的变化长度,也能够到过来计数
  • do能够绑定多个变量,并在更新变量的时候完成隐式赋值,所以不须要使用一些显式的setf或者let,push,所以能够写出很优雅的代码

使用do的求阶乘函数

(defun fact (n)
  (do ((i n (- i 1))
       (result 1 (* result i)))
      ((zerop i) result)))

如今注意do的var list中的赋值,这里的机制是平行赋值,相似与let的方式,而let*则是逐步 赋值。好比fact(5),第一次初始化的时候i为5,result为1,而后测试test以后更新变量列表,i 的值变为4,可是result计算的时候仍是使用前面i = 5的值,获得result = 5, 这个更新能够理解为一步完成,即里面的变量的计算获得的新值暂时不会覆盖掉变量,等到整个 var list计算完成后,直接将全部变量一次更新。

当一些变量更新式须要判断条件的时候,将其写在var list中会显得很是臃肿影响可读性,最好在body中来进行操做

do能够同时遍历多个list,如要在两个list中查找第一个相同的元素(元素的位置也相同)

(defun find-matching-ele (x y)
  (do ((x1 x (rest x1))
       (y1 y (rest y1)))
      ((or (null x1) (null y1)) nil)
    (if (equal (first x1) (first y1)) (return (first x1)))))

11.9 do*

do*相似与let*,都是逐步对var list进行求值,除此以外用法与do同样

ex 11.11

(defun find-largest (list-of-numbers)
  (do* ((list1 list-of-numbers (rest list1))
        (largest (first list-of-numbers)))
       ((null list1) largest)
    (when (> (first list1) largest) (setf largest (first list1)))))

ex 11.12

(defun power-of-2 (n)
  (do ((i n (decf i 1))
       (result 1 (+ result result)))
      ((zerop i) result)))

ex 11.13

(defun first-non-integer (x)
  (dolist (e x 'none)
    (unless (integerp e) (return e))))

ex 11.15

(defun ffo-with-do (x)
  (do ((z x (rest z))
       (e (first x) (first z)))
      ((null z) nil)
    (when (oddp e) (return e))))

这段程序要返回list中的第一个出现的奇数,但存在一个问题,当第一个奇数是x中的最后一个元素的时候,将会 返回nil,由于这里用并行赋值的do,当z更新为nil,e将更新为最后一个元素,此时test已经返回nil结束函数,没有 机会再进入body

11.11 implicit blocks

block是一块表达式,能够包含多个语句,能够经过return from block-name来从这个块返回,而不去执行这个块 中return后面的表达式, 每一个块都有一个名字,如一个函数就是一个块名

好比函数square-list接受参数list,返回里面每一个元素的平方,若是遇到list中有不是数字的元素,则返回`nope

(defun square-list (x)
  (mapcar #'(lambda (e) (if numberp e) (* e e) (return-from square-list 'nope)) x))

(square-list '(1 2 3 4)) -> (1 4 9 16)
(square-list '(1 2 hello)) -> nope

函数在遇到不是数字的元素时,直接就从整个函数返回了,而不仅是从lambda函数和mapcar中返回

return-from函数从最里面的block开始找要返回的块名。通常的循环函数dotimes,dolist,do,do*等都包含在一个隐式的块名nil中,前面的(return x)其实是(return-from nil x)的简写

ex 11.18

(do ((i 0 (+ 1 i)))
    ((equal i 5) i)
    (format t "~&i = ~S" i))

ex 11.21

(defun fib (n)
  "n should > 0"
  (do ((i 2 (+ i 1))
       (cur 1 (+ cur prev))
       (prev 0 cur))
      ((> i n) cur)))

lisp toolkit: time

time macro function能够获取计算一个表达式所用的时间,内存,和其余一些有用信息

> (time (+ 1 1))
real time: ... sec.
run time: ... sec.
space: ... bytes

11.13 optional args

函数中加入&optional可使用缺省参数,默认为nil

(defun foo (x &optional y)
  (format t "~&x is ~S" x)
  (format t "~&y is ~S" y)
  (list x y))

> (foo 3 5)
x is 3
y is 5
(3 5)

> (foo 3)
x is 3
y is nil
(3 nil)

能够本身设定缺省值,如&optional (name value)

11.14 rest args

&rest后面跟的参数会以一个list的形式传递,所以能够用来传递任意数量的参数

(defun average (&rest args)
  (/ (reduce #'+ args) (length args) 1.0))

使用递归的时候要很是注意&rest的使用,由于第一次调用的时候将参数打包成一个list,这样第二次调用的时候, 会再将这个list做为第一个元素放到一个list中,变成双重的list。要解决这个问题可使用apply,递归调用的 时候仍然只是一个list的形式,以下面函数求一个list的元素的平方数

(defun square-all (&rest args)
  (if (null args) nil 
    (cons (* (first args) (first args)) (apply #'square-all (cdr args)))))

11.15 keyword args

前面使用过的关键字参数如member中设置比较用的函数,(member x y :test #'equal)

使用&key能够本身定义函数的关键字参数,&key后面能够跟任意的关键字参数,使用默认值的方式和&optional 相同,形如(key value). 注意调用时关键字参数前面有冒号:

11.16 &aux vars

&aux能够用来定义辅助的局部变量,使用方法:&aux (name [var-expression]),定义一个名为name的局部变量, 经过计算var-expression的值获得初始化的值,若是只写name则默认初始化为nil

如求平均值函数中使用辅助变量保存list的长度

(defun average (&rest args &aux (len (length args))) (/ (reduce #'+ args) len 1.0))

&aux的功能均可以使用let*来实现,都是建立新的局部变量,并且时逐个赋值

相关文章
相关标签/搜索