介绍

loop
Common Lisp 内建宏。最简单用法是 (loop (print "hello")):这是个死循环,会一直打印 “hello”。

简单的列表迭代:

  1. (loop for x in '(1 2 3)
  2. do (print x))

代码输出需要的字符,返回 nil
使用 collect 来返回列表:

  1. (loop for x in '(1 2 3)
  2. collect (* x 10))
  3. ;; (10 20 30)

Loop 宏与大部分 Lisp 表达式不同,其内部处理的是一种特定的语法,而不是使用 S 表达式。因此,当阅读 loop 表达式时,你需要将一半的注意力放在 Lisp 语法上,另一半注意力放在 Loop 语法上。这让你对 Loop 是爱恨交加。

可以将 Loop 表达式看作四个部分:循环变量,终止条件,循环内容,循环终止后的内容。此外, Loop 表达式也会返回一个值。虽然这个部分并不会全部用到,但可以用不同的方法进行组合。

iterate
更简单、更 “lisper”、比 loop 更可控的循环宏。然而,它并不是内建的,使用时需要导入:

  1. (ql:quickload :iterate)
  2. (use-package :iterate)

Iterate 看起来是这样:

  1. (iter (for i from 1 to 5)
  2. (collect (* i i)))

注:loop 和 iterate 无法在同一个包中使用,会出现名字冲突
Iterate 同时有 display-iterate-clauses 可以查看其中的迭代

  1. (display-iterate-clauses '(fo))
  2. ;; FOR PREVIOUS &OPTIONAL INITIALLY BACK Previous value of a variable
  3. ;; FOR FIRST THEN Set var on first, and then on subsequent iterations
  4. ;; ...

for
一个比 loop 更短的迭代,但不像 loop 那么可拓展,也不像 iterate 那样需要遍历代码但是拓展很简单。
在遍历所有的数据结构上有很大的优势,如果不确定的话,就用 for ... over

  1. (for:for ((x over <your data structure>))
  2. (print ...))

当然,for 也需要使用 quickload 加载进来:

  1. (ql:quickload :for)

以下是一些关于 mapcarmap 例子:

  • mapcar
  1. (mapcar (lambda (it) (+ it 10)) '(1 2 3))
  2. (11 12 13)
  • map
  1. (map 'vector (lambda (it) (+ it 10)) '(1 2 3))
  2. ;; #(11 12 13)
  3. (map 'list (lambda (it) (+ it 10)) #(1 2 3))
  4. ;; (11 12 13)
  5. (map 'string (lambda (it) (code-char it)) '#(97 98 99))
  6. ;; "abc"

要是经常使用 mapcar 的话,可以使用一些 lambda shorthand libararies 库。以下是关于 cl-punch 的例子:

  1. (mapcar ^(* _ 10) '(1 2 3))
  2. ;; (10 20 30)

但是呢,在本节中不会这样写,但只要你愿意的话,怎么方便怎么写。

还有,序列可能是像这样的:

  1. (collect
  2. (mapping ((x (scan-range :from 1 :upto 5)))
  3. (* x x)))
  4. ;; (1 4 9 16 25)

实例(Recipes)

死循环

  1. (loop
  2. (print "hello"))

return 会返回相应的结果:

  1. (loop for i in '(1 2 3)
  2. when (> i 1)
  3. return i)
  4. 2

循环次数

dotimes

  1. (dotimes (n 10)
  2. (print n))

返回值为 nil,同时也可以在循环最后使用 return

  1. (dotimes (i 10)
  2. (if (> i 3)
  3. (return)
  4. (print i)))

loop … repeat

  1. (loop repeat 10
  2. do (format t "Hello!~%"))

以上代码将打印十行 ‘Hello’,返回 nil

  1. (loop repeat 10 collect (random 10))
  2. ;; (5 1 3 5 4 0 7 4 9 1)

使用 collect 时可以返回列表。

series

  1. (iterate ((n (scan-range :below 10)))
  2. (print n))

loop 迭代

列表和向量的迭代:

  1. (iter (for item in '(1 2 3))
  2. (print item))
  3. (iter (for i in-vector #(1 2 3))
  4. (print i))

哈希表的迭代:

  1. (let ((h (let ((h (make-hash-table)))
  2. (setf (gethash 'a h) 1)
  3. (setf (gethash 'b h) 2)
  4. h)))
  5. (iter (for (k v) in-hashtable h)
  6. (print k)))
  7. ;; b
  8. ;; a

实际上,你可以参考下这里,或者使用 (display-iterate-clauses '(for)) 来查看:

  • 包中的变量
  • 格式/行/文件/流 随你怎么说
  • 序列中的元素

遍历列表

dolist

  1. (dolist (item '(1 2 3))
  2. (print item))

loop

in

  1. (loop for x in '(a b c)
  2. do (print x))
  3. ;; A
  4. ;; B
  5. ;; C
  6. ;; NIL

on

  1. (loop for i on '(1 2 3) do (print i))
  2. ;; (1 2 3)
  3. ;; (2 3)
  4. ;; (3)

mapcar

  1. (mapcar (lambda (x)
  2. (print (* x 10)))
  3. '(1 2 3))
  4. 10
  5. 20
  6. 30
  7. (10 20 30)

series

  1. (iterate ((item (scan '(1 2 3))))
  2. (print item))

scan-sublists 等价于 loop for ... on

  1. (iterate ((i (scan-sublists '(1 2 3))))
  2. (print i))

遍历向量

loop: across

  1. (loop for i across #(1 2 3) do (print i))

series

  1. (iterate ((i (scan #(1 2 3))))
  2. (print i))

遍历哈希表

先创建一个哈希表

  1. (setf h (make-hash-table))
  2. (setf (gethash 'a h) 1)
  3. (setf (gethash 'b h) 2)

loop

  1. (loop for k being the hash-key of h do (print k))
  2. ;; A
  3. ;; B
  4. ;; NIL
  5. (loop for v being the hash-value of h do (print v))
  6. ;; 1
  7. ;; 2
  8. ;; NIL
  9. (loop for k
  10. being the hash-key
  11. using (hash-value v) of h
  12. do (format t "~a ~a~%" k v))
  13. ;; A 1
  14. ;; B 2
  15. ;; NIL

for

  1. (for:for ((it over h))
  2. (print it))
  3. (A 1)
  4. (B 2)
  5. NIL

maphash

  1. (maphash (lambda (key val)
  2. (format t "key: ~a val:~a~&" kev val))
  3. h)
  4. ;; key: A val:1
  5. ;; key: B val:2
  6. ;; NIL

更多参考 with-hash-table-iterator

series

  1. (iterate (((k v) (scan-hash h)))
  2. (format t "~&~a ~a~%" k v))

并行遍历列表

loop

  1. (loop for x in '(a b c)
  2. for y in '(1 2 3)
  3. collect (list x y))
  4. ;; ((A 1) (B 2) (C 3))

mapcar

  1. (mapcar (lambda (x y)
  2. (list x y))
  3. '(a b c)
  4. '(1 2 3))
  5. ;; ((A 1) (B 2) (C 3))

简单点:

  1. (mapcar #'list
  2. '(a b c)
  3. '(1 2 3))
  4. ;; ((A 1) (B 2) (C 3))

返回一个单列表

  1. (mapcan (lambda (x y)
  2. (list x y))
  3. '(a b c)
  4. '(1 2 3))
  5. ;; (A 1 B 2 C 3)

series

  1. (collect
  2. (#Mlist (scan '(a b c))
  3. (scan '(1 2 3))))

更高效的方法

  1. (collect
  2. (mapping (((x y) (scan-multiple 'list
  3. '(a b c)
  4. '(1 2 3))))
  5. (list x y)))

返回一个单列表

  1. (collect-append ; or collect-nconc
  2. (mapping (((x y) (scan-multiple 'list
  3. '(a b c)
  4. '(1 2 3))))
  5. (list x y)))

循环嵌套

loop

  1. (loop for x from 1 to 3
  2. collect (loop for y from 1 to x
  3. collect y))
  4. ;; ((1) (1 2) (1 2 3))

iterate

  1. (iter outer
  2. (for i below 2)
  3. (iter (for j below 3)
  4. (in outer (collect (list i j)))))
  5. ;; ((0 0) (0 1) (0 2) (1 0) (1 1) (1 2))

series

  1. (collect
  2. (mapping ((x (scan-range :from 1 :upto 3)))
  3. (collect (scan-range :from 1 :upto x))))

计算中间值

=

  1. (loop for x from 1 to 3
  2. for y = (* x 10)
  3. collect y)
  4. ;; (10 20 30)

循环中的计数器

loop

  1. (loop for x in '(a b c d e)
  2. for y from 1
  3. when (> y 1)
  4. do (format t ", ")
  5. do (format t "~A" x)
  6. )
  7. ;; A, B, C, D, E
  8. ;; NIL
  9. (loop for x in '(a b c d e)
  10. for y from 1
  11. if (> y 1)
  12. do (format t ", ~A" x)
  13. else do (format t "~A" x))
  14. ;; A, B, C, D, E
  15. ;; NIL

series

  1. (iterate ((x (scan '(a b c d e)))
  2. (y (scan-range :from 1)))
  3. (when (> y 1) (format t ", "))
  4. (format t "~A" x))

增序、降序、极限

loop

from... to...:

  1. (loop for i from 0 to 10
  2. do (print i))
  3. ;; 0 1 2 3 4 5 6 7 8 9 10

from... below...:

  1. (loop for i from 0 below 10
  2. do (print i))
  3. ;; 0 1 2 3 4 5 6 7 8 9

from 10 downto 0 => (10 … 0) 或 from 10 above 0 (10 … 1)

series

:from ... :upto

  1. (iterate ((i (scan-range :from 0 :upto 10)))
  2. (print i))

:from ... :below

  1. (iterate ((i (scan-range :from 0 :below 10)))
  2. (print i))

步长

loop

by

  1. (loop for i from 1 to 10 by 2
  2. do (print i))
  3. (let ((step (random 3)))
  4. (loop for i from 1 to 10 by (+ 1 step)
  5. do (print i)))

series

:by

  1. (iterate ((i (scan-range :from 1 :upto 10 :by 2)))
  2. (print i))

循环与条件

loop

  1. ;; if, else, finally
  2. ;; https://riptutorial.com/common-lisp/example/11095/conditionally-executing-loop-clauses
  3. (loop repeat 10
  4. for x = (random 100)
  5. if (evenp x)
  6. collect x into evens
  7. else
  8. collect x into odds
  9. finally (return (values evens odds)))
  10. ;; (42 82 24 92 92)
  11. ;; (55 89 59 13 49)
  12. ;; and do, and count
  13. (loop repeat 10
  14. for x = (random 100)
  15. if (evenp x)
  16. collect x into evens
  17. and do (format t "~a is even!~%" x)
  18. else
  19. collect x into odds
  20. and count t into n-odds
  21. finally (return (values evens odds n-odds)))
  22. ;; 46 is even!
  23. ;; 8 is even!
  24. ;; 76 is even!
  25. ;; 58 is even!
  26. ;; 0 is even!
  27. ;; (46 8 76 58 0)
  28. ;; (7 45 43 15 69)
  29. ;; 5

iterate

  1. (iter (repeat 10)
  2. (for x = (random 100))
  3. (if (evenp x)
  4. (progn
  5. (collect x into evens)
  6. (format t "~a is even!~%" x))
  7. (progn
  8. (collect x into odds)
  9. (count t into n-odds)))
  10. (finally (return (values evens odds n-odds))))

series

  1. (let* ((number (#M(lambda (n) (random 100))
  2. (scan-range :below 10)))
  3. (parity (#Mevenp number)))
  4. (iterate ((n number) (p parity))
  5. (when p (format t "~a is even!~%" n)))
  6. (multiple-value-bind (evens odds) (split number parity)
  7. (values (collect evens)
  8. (collect odds)
  9. (collect-length odds))))

循环终止/退出

loop

  1. (loop for x in '(1 2 3 4 5)
  2. until (> x 3)
  3. collect x)
  4. ;; (1 2 3)

while 也一样:

  1. (loop for x in '(1 2 3 4 5)
  2. while (< x 4)
  3. collect x)

series

  1. (collect
  2. (until-if (lambda (i) (> i 3))
  3. (scan '(1 2 3 4 5))))

循环输出及返回结果

loop

  1. (loop for x in '(1 2 3 4 5)
  2. while (< x 4)
  3. do (format t "x is ~a~&" x)
  4. collect x)
  5. ;; x is 1
  6. ;; x is 2
  7. ;; x is 3
  8. ;; (1 2 3)
  • series
    1. (collect
    2. (mapping ((x (until-if (complement (lambda (x) (< x 4)))
    3. (scan '(1 2 3 4 5)))))
    4. (format t "x is ~a~&" x)
    5. x))

循环命名及提前退出

loop

  1. ;; useless example
  2. (loop named loop-1
  3. for x from 0 to 10 by 2
  4. do (loop for y from 0 to 100 by (1+ (random 3))
  5. when (< x y)
  6. do (return-from loop-1 (values x y))))
  7. ;; 0
  8. ;; 2

when/return 速记

  1. (loop for x in '(foo 2)
  2. thereis (numberp x))
  3. ;; T
  4. (loop for x in '(foo 2)
  5. never (numberp x))
  6. ;; NIL
  7. (loop for x in '(foo 2)
  8. always (numberp x))
  9. ;; NIL

series

  1. (block loop-1
  2. (iterate ((x (scan-range :from 0 :upto 10 :by 2)))
  3. (iterate ((y (scan-range :from 0 :upto 100 :by (1+ (random 3)))))
  4. (when (< x y)
  5. (return-from loop-1 (values x y))))))

Count

loop

  1. (loop for i from 1 to 3 count (oddp i))
  2. ;; 2

series

  1. (collect-length (choose-if #'oddp (scan-range :from 1 :upto 3)))

累加

loop

  1. (loop for i from 1 to 3 sum (* i i))
  2. ;; 14
  3. ;; summing into a variable
  4. (loop for i from 1 to 3
  5. sum (* i i) into total
  6. do (print i)
  7. finally (print total))
  8. ;; 1
  9. ;; 2
  10. ;; 3
  11. ;; 14

series

  1. (collect-sum (#M(lambda (i) (* i i))
  2. (scan-range :from 1 :upto 3)))

max/min

loop

  1. (loop for i from 1 to 3 maximize (mod i 3))
  2. ;; 2

series

  1. (collect-max (#M(lambda (i) (mod i 3))
  2. (scan-range :from 1 :upto 3)))

destructuring,又称列表或点对的模式匹配

loop

  1. (loop for (a b) in '((x 1) (y 2) (z 3))
  2. collect (list b a))
  3. ;; ((1 X) (2 Y) (3 Z))
  4. (loop for (x . y) in '((1 . a) (2 . b) (3 . c)) collect y)
  5. ;; (A B C)
  6. ;; use nil to ignore a term
  7. (loop for (a nil) in '((x 1) (y 2) (z 3))
  8. collect a)
  9. ;; (X Y Z)

Iterating 2 by 2 over a list

To iterate over a list, 2 items at a time we use a combination of on, by and destructuring.

We use on to loop over the rest (the cdr) of the list.

  1. (loop for rest on '(a 2 b 2 c 3)
  2. collect rest)
  3. ;; ((A 2 B 2 C 3) (2 B 2 C 3) (B 2 C 3) (2 C 3) (C 3) (3))

We use by to skip one element at every iteration ((cddr list) is equivalent to (rest (rest list)))

  1. (loop for rest on '(a 2 b 2 c 3) by #'cddr
  2. collect rest)
  3. ;; ((A 2 B 2 C 3) (B 2 C 3) (C 3))

Then we add destructuring to bind only the first two items at each iteration:

  1. (loop for (key value) on '(a 2 b 2 c 3) by #'cddr
  2. collect (list key (* 2 value)))
  3. ;; ((A 2) (B 4) (C 6))

series

  1. (collect
  2. (mapping ((l (scan '((x 1) (y 2) (z 3)))))
  3. (destructuring-bind (a b) l
  4. (list b a))))
  5. ;; only for alists
  6. (collect
  7. (mapping (((a b) (scan-alist '((1 . a) (2 . b) (3 . c)))))
  8. b))

自定义序列扫描器

  1. (defun scan-listlist (listlist)
  2. (declare (optimizable-series-function 2))
  3. (map-fn '(values t t)
  4. (lambda (l)
  5. (destructuring-bind (a b) l
  6. (values a b)))
  7. (scan listlist)))
  8. (collect
  9. (mapping (((a b) (scan-listlist '((x 1) (y 2) (z 3)))))
  10. (list b a)))

简化序列表达式

  1. (collect-sum (mapping ((i (scan-range :length 5)))
  2. (* i 2)))
  1. (collect-sum (* 2 (scan-range :length 5)))

写成这样前要先设置一下:

  1. (series::install :implicit-map t)

Loop 小提示

  • 注意关键词 it,经常会在函数的构建中使用,会被当作是 loop 中的关键词。所以不要在循环中使用。

附录:loop 关键词列表

Name 分句

  1. named

变量分句

  1. initially finally for as with

主分句

  1. do collect collecting append
  2. appending nconc nconcing into count
  3. counting sum summing maximize return
  4. maximizing minimize minimizing doing
  5. thereis always never if when
  6. unless repeat while until

以下的语句在文中没有介绍

  1. = and it else end from upfrom
  2. above below to upto downto downfrom
  3. in on then across being each the hash-key
  4. hash-kes of using hash-value hash-values
  5. symbol symbols present-symbol
  6. present-symbols external-symbol
  7. external-symbols fixnum float t nil of-type

需要注意的是,只有在解析时才能决定到底是不是关键词。例如:

  1. (loop for key in hash-values)

在这当中只有 forin 是关键词

参考资料

循环

迭代

Series

Others