介绍
loop 是
Common Lisp 内建宏。最简单用法是 (loop (print "hello"))
:这是个死循环,会一直打印 “hello”。
简单的列表迭代:
(loop for x in '(1 2 3)
do (print x))
代码输出需要的字符,返回 nil
使用 collect
来返回列表:
(loop for x in '(1 2 3)
collect (* x 10))
;; (10 20 30)
Loop 宏与大部分 Lisp 表达式不同,其内部处理的是一种特定的语法,而不是使用 S 表达式。因此,当阅读 loop 表达式时,你需要将一半的注意力放在 Lisp 语法上,另一半注意力放在 Loop 语法上。这让你对 Loop 是爱恨交加。
可以将 Loop 表达式看作四个部分:循环变量,终止条件,循环内容,循环终止后的内容。此外, Loop 表达式也会返回一个值。虽然这个部分并不会全部用到,但可以用不同的方法进行组合。
iterate
更简单、更 “lisper”、比 loop
更可控的循环宏。然而,它并不是内建的,使用时需要导入:
(ql:quickload :iterate)
(use-package :iterate)
Iterate 看起来是这样:
(iter (for i from 1 to 5)
(collect (* i i)))
注:loop 和 iterate 无法在同一个包中使用,会出现名字冲突
Iterate 同时有 display-iterate-clauses
可以查看其中的迭代
(display-iterate-clauses '(fo))
;; FOR PREVIOUS &OPTIONAL INITIALLY BACK Previous value of a variable
;; FOR FIRST THEN Set var on first, and then on subsequent iterations
;; ...
for 是
一个比 loop 更短的迭代,但不像 loop 那么可拓展,也不像 iterate 那样需要遍历代码但是拓展很简单。
在遍历所有的数据结构上有很大的优势,如果不确定的话,就用 for ... over
:
(for:for ((x over <your data structure>))
(print ...))
当然,for 也需要使用 quickload 加载进来:
(ql:quickload :for)
以下是一些关于 mapcar
和 map
例子:
mapcar
(mapcar (lambda (it) (+ it 10)) '(1 2 3))
(11 12 13)
map
(map 'vector (lambda (it) (+ it 10)) '(1 2 3))
;; #(11 12 13)
(map 'list (lambda (it) (+ it 10)) #(1 2 3))
;; (11 12 13)
(map 'string (lambda (it) (code-char it)) '#(97 98 99))
;; "abc"
要是经常使用 mapcar
的话,可以使用一些 lambda shorthand libararies 库。以下是关于 cl-punch 的例子:
(mapcar ^(* _ 10) '(1 2 3))
;; (10 20 30)
但是呢,在本节中不会这样写,但只要你愿意的话,怎么方便怎么写。
还有,序列可能是像这样的:
(collect
(mapping ((x (scan-range :from 1 :upto 5)))
(* x x)))
;; (1 4 9 16 25)
实例(Recipes)
死循环
(loop
(print "hello"))
return
会返回相应的结果:
(loop for i in '(1 2 3)
when (> i 1)
return i)
2
循环次数
dotimes
(dotimes (n 10)
(print n))
返回值为 nil
,同时也可以在循环最后使用 return
(dotimes (i 10)
(if (> i 3)
(return)
(print i)))
loop … repeat
(loop repeat 10
do (format t "Hello!~%"))
以上代码将打印十行 ‘Hello’,返回 nil
(loop repeat 10 collect (random 10))
;; (5 1 3 5 4 0 7 4 9 1)
使用 collect
时可以返回列表。
series
(iterate ((n (scan-range :below 10)))
(print n))
loop 迭代
列表和向量的迭代:
(iter (for item in '(1 2 3))
(print item))
(iter (for i in-vector #(1 2 3))
(print i))
哈希表的迭代:
(let ((h (let ((h (make-hash-table)))
(setf (gethash 'a h) 1)
(setf (gethash 'b h) 2)
h)))
(iter (for (k v) in-hashtable h)
(print k)))
;; b
;; a
实际上,你可以参考下这里,或者使用 (display-iterate-clauses '(for))
来查看:
- 包中的变量
- 格式/行/文件/流 随你怎么说
- 序列中的元素
遍历列表
dolist
(dolist (item '(1 2 3))
(print item))
loop
in
(loop for x in '(a b c)
do (print x))
;; A
;; B
;; C
;; NIL
on
(loop for i on '(1 2 3) do (print i))
;; (1 2 3)
;; (2 3)
;; (3)
mapcar
(mapcar (lambda (x)
(print (* x 10)))
'(1 2 3))
10
20
30
(10 20 30)
series
(iterate ((item (scan '(1 2 3))))
(print item))
scan-sublists
等价于 loop for ... on
:
(iterate ((i (scan-sublists '(1 2 3))))
(print i))
遍历向量
loop: across
(loop for i across #(1 2 3) do (print i))
series
(iterate ((i (scan #(1 2 3))))
(print i))
遍历哈希表
先创建一个哈希表
(setf h (make-hash-table))
(setf (gethash 'a h) 1)
(setf (gethash 'b h) 2)
loop
(loop for k being the hash-key of h do (print k))
;; A
;; B
;; NIL
(loop for v being the hash-value of h do (print v))
;; 1
;; 2
;; NIL
(loop for k
being the hash-key
using (hash-value v) of h
do (format t "~a ~a~%" k v))
;; A 1
;; B 2
;; NIL
for
(for:for ((it over h))
(print it))
(A 1)
(B 2)
NIL
maphash
(maphash (lambda (key val)
(format t "key: ~a val:~a~&" kev val))
h)
;; key: A val:1
;; key: B val:2
;; NIL
更多参考 with-hash-table-iterator。
series
(iterate (((k v) (scan-hash h)))
(format t "~&~a ~a~%" k v))
并行遍历列表
loop
(loop for x in '(a b c)
for y in '(1 2 3)
collect (list x y))
;; ((A 1) (B 2) (C 3))
mapcar
(mapcar (lambda (x y)
(list x y))
'(a b c)
'(1 2 3))
;; ((A 1) (B 2) (C 3))
简单点:
(mapcar #'list
'(a b c)
'(1 2 3))
;; ((A 1) (B 2) (C 3))
返回一个单列表
(mapcan (lambda (x y)
(list x y))
'(a b c)
'(1 2 3))
;; (A 1 B 2 C 3)
series
(collect
(#Mlist (scan '(a b c))
(scan '(1 2 3))))
更高效的方法
(collect
(mapping (((x y) (scan-multiple 'list
'(a b c)
'(1 2 3))))
(list x y)))
返回一个单列表
(collect-append ; or collect-nconc
(mapping (((x y) (scan-multiple 'list
'(a b c)
'(1 2 3))))
(list x y)))
循环嵌套
loop
(loop for x from 1 to 3
collect (loop for y from 1 to x
collect y))
;; ((1) (1 2) (1 2 3))
iterate
(iter outer
(for i below 2)
(iter (for j below 3)
(in outer (collect (list i j)))))
;; ((0 0) (0 1) (0 2) (1 0) (1 1) (1 2))
series
(collect
(mapping ((x (scan-range :from 1 :upto 3)))
(collect (scan-range :from 1 :upto x))))
计算中间值
=
(loop for x from 1 to 3
for y = (* x 10)
collect y)
;; (10 20 30)
循环中的计数器
loop
(loop for x in '(a b c d e)
for y from 1
when (> y 1)
do (format t ", ")
do (format t "~A" x)
)
;; A, B, C, D, E
;; NIL
(loop for x in '(a b c d e)
for y from 1
if (> y 1)
do (format t ", ~A" x)
else do (format t "~A" x))
;; A, B, C, D, E
;; NIL
series
(iterate ((x (scan '(a b c d e)))
(y (scan-range :from 1)))
(when (> y 1) (format t ", "))
(format t "~A" x))
增序、降序、极限
loop
from... to...
:
(loop for i from 0 to 10
do (print i))
;; 0 1 2 3 4 5 6 7 8 9 10
from... below...
:
(loop for i from 0 below 10
do (print i))
;; 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
(iterate ((i (scan-range :from 0 :upto 10)))
(print i))
:from ... :below
(iterate ((i (scan-range :from 0 :below 10)))
(print i))
步长
loop
by
(loop for i from 1 to 10 by 2
do (print i))
(let ((step (random 3)))
(loop for i from 1 to 10 by (+ 1 step)
do (print i)))
series
:by
(iterate ((i (scan-range :from 1 :upto 10 :by 2)))
(print i))
循环与条件
loop
;; if, else, finally
;; https://riptutorial.com/common-lisp/example/11095/conditionally-executing-loop-clauses
(loop repeat 10
for x = (random 100)
if (evenp x)
collect x into evens
else
collect x into odds
finally (return (values evens odds)))
;; (42 82 24 92 92)
;; (55 89 59 13 49)
;; and do, and count
(loop repeat 10
for x = (random 100)
if (evenp x)
collect x into evens
and do (format t "~a is even!~%" x)
else
collect x into odds
and count t into n-odds
finally (return (values evens odds n-odds)))
;; 46 is even!
;; 8 is even!
;; 76 is even!
;; 58 is even!
;; 0 is even!
;; (46 8 76 58 0)
;; (7 45 43 15 69)
;; 5
iterate
(iter (repeat 10)
(for x = (random 100))
(if (evenp x)
(progn
(collect x into evens)
(format t "~a is even!~%" x))
(progn
(collect x into odds)
(count t into n-odds)))
(finally (return (values evens odds n-odds))))
series
(let* ((number (#M(lambda (n) (random 100))
(scan-range :below 10)))
(parity (#Mevenp number)))
(iterate ((n number) (p parity))
(when p (format t "~a is even!~%" n)))
(multiple-value-bind (evens odds) (split number parity)
(values (collect evens)
(collect odds)
(collect-length odds))))
循环终止/退出
loop
(loop for x in '(1 2 3 4 5)
until (> x 3)
collect x)
;; (1 2 3)
while
也一样:
(loop for x in '(1 2 3 4 5)
while (< x 4)
collect x)
series
(collect
(until-if (lambda (i) (> i 3))
(scan '(1 2 3 4 5))))
循环输出及返回结果
loop
(loop for x in '(1 2 3 4 5)
while (< x 4)
do (format t "x is ~a~&" x)
collect x)
;; x is 1
;; x is 2
;; x is 3
;; (1 2 3)
- series
(collect
(mapping ((x (until-if (complement (lambda (x) (< x 4)))
(scan '(1 2 3 4 5)))))
(format t "x is ~a~&" x)
x))
循环命名及提前退出
loop
;; useless example
(loop named loop-1
for x from 0 to 10 by 2
do (loop for y from 0 to 100 by (1+ (random 3))
when (< x y)
do (return-from loop-1 (values x y))))
;; 0
;; 2
when/return 速记
(loop for x in '(foo 2)
thereis (numberp x))
;; T
(loop for x in '(foo 2)
never (numberp x))
;; NIL
(loop for x in '(foo 2)
always (numberp x))
;; NIL
series
(block loop-1
(iterate ((x (scan-range :from 0 :upto 10 :by 2)))
(iterate ((y (scan-range :from 0 :upto 100 :by (1+ (random 3)))))
(when (< x y)
(return-from loop-1 (values x y))))))
Count
loop
(loop for i from 1 to 3 count (oddp i))
;; 2
series
(collect-length (choose-if #'oddp (scan-range :from 1 :upto 3)))
累加
loop
(loop for i from 1 to 3 sum (* i i))
;; 14
;; summing into a variable
(loop for i from 1 to 3
sum (* i i) into total
do (print i)
finally (print total))
;; 1
;; 2
;; 3
;; 14
series
(collect-sum (#M(lambda (i) (* i i))
(scan-range :from 1 :upto 3)))
max/min
loop
(loop for i from 1 to 3 maximize (mod i 3))
;; 2
series
(collect-max (#M(lambda (i) (mod i 3))
(scan-range :from 1 :upto 3)))
destructuring,又称列表或点对的模式匹配
loop
(loop for (a b) in '((x 1) (y 2) (z 3))
collect (list b a))
;; ((1 X) (2 Y) (3 Z))
(loop for (x . y) in '((1 . a) (2 . b) (3 . c)) collect y)
;; (A B C)
;; use nil to ignore a term
(loop for (a nil) in '((x 1) (y 2) (z 3))
collect a)
;; (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.
(loop for rest on '(a 2 b 2 c 3)
collect rest)
;; ((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))
)
(loop for rest on '(a 2 b 2 c 3) by #'cddr
collect rest)
;; ((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:
(loop for (key value) on '(a 2 b 2 c 3) by #'cddr
collect (list key (* 2 value)))
;; ((A 2) (B 4) (C 6))
series
(collect
(mapping ((l (scan '((x 1) (y 2) (z 3)))))
(destructuring-bind (a b) l
(list b a))))
;; only for alists
(collect
(mapping (((a b) (scan-alist '((1 . a) (2 . b) (3 . c)))))
b))
自定义序列扫描器
(defun scan-listlist (listlist)
(declare (optimizable-series-function 2))
(map-fn '(values t t)
(lambda (l)
(destructuring-bind (a b) l
(values a b)))
(scan listlist)))
(collect
(mapping (((a b) (scan-listlist '((x 1) (y 2) (z 3)))))
(list b a)))
简化序列表达式
(collect-sum (mapping ((i (scan-range :length 5)))
(* i 2)))
(collect-sum (* 2 (scan-range :length 5)))
写成这样前要先设置一下:
(series::install :implicit-map t)
Loop 小提示
- 注意关键词
it
,经常会在函数的构建中使用,会被当作是 loop 中的关键词。所以不要在循环中使用。
附录:loop 关键词列表
Name 分句
named
变量分句
initially finally for as with
主分句
do collect collecting append
appending nconc nconcing into count
counting sum summing maximize return
maximizing minimize minimizing doing
thereis always never if when
unless repeat while until
以下的语句在文中没有介绍
= and it else end from upfrom
above below to upto downto downfrom
in on then across being each the hash-key
hash-kes of using hash-value hash-values
symbol symbols present-symbol
present-symbols external-symbol
external-symbols fixnum float t nil of-type
需要注意的是,只有在解析时才能决定到底是不是关键词。例如:
(loop for key in hash-values)
在这当中只有 for
和 in
是关键词
参考资料
循环
- Tutorial for the Common Lisp Loop Macro
- http://www.unixuser.org/~euske/doc/cl/loop.html
- riptutorial.com
迭代
- The Iterate Manual
- itera - highlights at a glance and examples
- Loop v Iterate - SabraOnTheHill
Series
Others
- See also: more functional constructs (do-repeat, take, …)