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*來實現,都是建立新的局部變量,並且時逐個賦值

相關文章
相關標籤/搜索