Contents

Common-Lisp loop and iteration

Introduction: loop,iterate,for,mapcar,series

loop

loop 是一个内置的用来迭代的宏, 它最简单的形式是(loop (print “hello”)): 这个语句会无限打印"hello" 一个简单的迭代List的例子

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

这个例子会返回Nil但是会打印他应该打印的 如果你需要将结果收集成一个list使用collect

1
2
(loop for x in '(1 2 3)
      collect (* x 10))

loop 表达式有四个部分

  • 设置要被迭代的变量
  • 结束迭代的条件表达式
  • 每次迭代要做的事情
  • 每次退出时要做的事情
  • 除此之外,loop表达式还能返回一个值

正常来说,我们很少使用所有的部分,但是我们可以随意组合他们

iterate

iterate 是一个很流行的迭代宏,要比loop更容易懂,但是拓展性要差一些。iterate并不是内置的,要先导入a。

1
2
(ql:quickload "iterate")
(ues-package :iterate)

iterate看起来是这样的

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

如果同时使用iterate 和 loop 会有命名冲突 使用display-iterate-clauses可以解决这个问题

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

for

for是一个可拓展的迭代宏,通常要比loop短。 for的最大好处就是,可以用在任何数据类型上(lists,vectors,hash-tables…)

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

for是一个第三方库,需要先quickload

1
(ql:quickload "for")

map族

后面还有很多mapcar 和 map这样的例子。map 族还有mapcon,mapcan,maplist,mapc 和 mapl.

1
2
(mapcar (lambda (it) (+ it 10)) '(1 2 3))
;; => (11 12 13)

map 更加通用 他可以接受List 和 vectors 作为参数,需要在第一个参数指定结果类型

1
2
3
4
5
6
(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"
简写lambda函数

是不是觉得写lambda太烦, 其实有一些库可以提供简写lambda函数方法你可以去这里看看简写lambda的库有哪些lambda shorthand libraries 这里给出一个cl-punch的例子

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

series

最后 你可能会喜欢series,一个库通过结合sequences,streams,和loop 来描述自己. Series 表达式看起来就像是在操作序列,但是可以获得相比loop 更高的效率。 Series第一次出现在 “Common Lisp the Language”

1
2
3
4
(collect
    (mapping ((x (scan-range :from 1 :upto 5)))
             (* x x)))
;; => (1 4 9 16 25)
Generators The Way I Want Them Generated Library

这是一个lazy sequences 库,和series类似,景观他很年轻,切不完全,但是他有很多现代化的API 比如take,filter,for,fold 并且易用

1
2
3
4
5
range :from 20)
;; #<GTWIWTG::GENERATOR! {1001A90CA3}>

(take 4 (range :from 20))
;; (20 21 22 23)

小妙招

Looping forever,return

1
2
(loop
  (print "hello"))

return 用来返回结果

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

固定循环n次

dotimes

1
2
(dotimes (n 3)
    (print n))

这里dotimes只会返回Nil 有两种方法返回一个值

  • 设置result在lambda list 中
    1
    2
    3
    4
    5
    6
    7
    
    (dotimes (n 3 :done)
      print(n))
    ;; =>
    ;; 0
    ;; 1
    ;; 2
    ;; :DONE
    
  • 使用return
    1
    2
    3
    4
    5
    6
    7
    8
    
    (dotimes (i 3)
      (if (> i 1)
          (return :early-exit!)
          (print i)))
    ;; =>
    ;; 0
    ;; 1
    ;; :EARLY-EXIT!
    

loop…repeat

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

打印10次hello 返回nil

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

使用collect 会返回一个list

Series

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

循环无限次,在一个循环list上循环

前面提到了一个无限循环的方法,但是我们如何在一个list上无限循环呢 我们可以构造一个循环list

1
2
3
4
5
(loop with list-a = '(1 2 3)
      with infinite-list = (setf (cdr (last list-a)) list-a)
      for item in infinite-list
      repeat 8
      collect item)

构造循环列表有一个非常简单的方法使用#=语法

1
2
3
(defparameter list-a '#1=(1 2 3 . #1#))
(setf print-circle t)
list-a

如果你只想再两个值之间交替地带,使用for…then

1
2
3
4
5
6
7
(loop repeat 4
      for up = t then (not up)
      do (print up))
T
NIL
T
NIL

Iterate 的for 循环

对于list 和 vectors:

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

在一个list上进行循环

dolist

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

loop

  • 使用in
1
2
3
4
5
6
(loop for x in '(a b c)
      do (print x))
;; A
;; B
;; C
;; NIL
1
2
3
(loop for x in '(a b c)
      collect x)
;; (A B C)
  • 使用on 我们在cdr上迭代
    1
    2
    3
    4
    
    (loop for i on '(1 2 3) do (print i))
    ;; (1 2 3)
    ;; (2 3)
    ;; (3)
    
  • mapcar
    1
    2
    3
    4
    5
    6
    7
    
    (mapcar (lambda (x)
              (print (* x 10)))
            '(1 2 3))
    10
    20
    30
    (10 20 30)
    

mapcar 会将lambda函数的返回值组合成一个List返回

在一个vector上循环

loop:across

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

Series

1
2
(iterate ((i (scan #(123))))
         (print i))

在一个hash-table上循环

先创建一个hasht-table:

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

loop

  • 在key上循环
    1
    2
    3
    
    (loop for k being the hash-key of h do(print k))
    ;; b
    ;; a
    
  • 在value上循环
    1
    2
    3
    4
    5
    6
    
    (loop for k
            being the hash-key
              using (hash-value v) of h
          do (format t "~a ~a~%" k v))
    ;; b 2
    ;; a 1
    

maphash

maphash 的lambda函数时一个拥有两个参数的函数两个参数分别是key,value

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

dohash

dohash 是第三方库trivial-do的一个macro,类似dolist

1
2
(dohash (key value h)
  (format t "key: ~A, value: ~A ~%" key value))

并行的在两个list上循环

loop

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

如果想返回一个平整过的list(flat list),使用nconcing 替代collect:

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

如果两个list的长度不同,会在短的结束的时候退出循环

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

我们可以在一个大的list上循环,并且手动的通过index访问小一点的List的元素,但是这样的效率是非常低的,我们可以让loop自动拓展短的list

1
2
3
4
5
(loop for y in '(1 2 3 4 5)
      for x-list = '(a b c) then (cdr x-list)
      for x = (or (car x-list) 'z)
      collect (list x y))
;; ((A 1) (B 2) (C 3) (Z 4) (Z 5))

在这个代码段中,for … = … then (cdr …) 在每一次的循环中都会缩短一次list. 他的值一开始是’(a b c) 然后是 ‘(b c) 然后 ‘(c) 最后 nil

mapcar

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

或者更简单:

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

返回一个flat list:

1
2
3
(mapcan (lambda (x y)
          (list x y))
        '(a b c))

嵌套循环(Nested loops)

loop

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

如果要返回一个flat list,使用nconcing 替换第一个collect

计算一个中间值

使用= 与 for结合

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

如果使用with,那么只会计算一次

1
2
3
4
5
(loop for x from 1 to 3
      for y = (* x 10)
      with z = x
      collect (list x y z))
;; ((1 10 1) (2 20 1) (3 30 1))

HyperSpec 对 with 的定义时这样的

1
with-clause::= with var1 [type-spec] [= form1] {and var2 [type-spec] [= form2]}*

所以我们可以再=前面指明类型 并且用and 串起来

1
2
3
4
(loop for x from 1 to 3
      for y integer = (* x 10)
      with z integer = x
      collect (list x y z))
1
2
3
4
(loop for x upto 3
      with foo = :foo
      and bar = :bar
      collect list (x foo bar))

我们也可以给for 一个 then 让他没次迭代都执行一次

1
2
3
4
5
6
(loop repeat 3
      for intermediate = 10 then (incf intermediate)
      do (print intermediate))
10
11
12

这里是一个在bool值之间不断切换的例子

1
2
3
4
5
6
7
(loop repeat 4
      for up = t then (not up)
      do (print up))
T
NIL
T
NIL

循环计数器

loop

对一个List进行迭代的同时进行计数。list的长度决定了迭代合适结束。

1
2
3
4
5
6
7
8
9
(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

也可以用if语句

1
2
3
4
5
6
7
8
9
(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

升降序,limits

loop

  • 升序

    • from… to…: include the last
      1
      2
      3
      
      (loop for i from 0 to 10
            do (print i))
      ;; 0 1 2 3 4 5 6 7 8 9 10
      
    • from… below…: not include the last
      1
      2
      3
      
      (loop for i from 0 below 10
            do (print i))
      ;; 0 1 2 3 4 5 6 7 8 9
      
  • 降序

    • from… downto…: include
      1
      2
      3
      
      (loop for i from 10 downto 0
            do (print i))
      ;; 10 9 8 7 6 5 4 3 2 1 0
      
    • from… above…: not include
      1
      2
      3
      
      (loop for i from 10 above 0
            do (print i))
      ;; 10 9 8 7 6 5 4 3 2 1
      

步长

loop

loop 使用by:

1
2
(loop for i from 1 to 10 by 2
      do (print i))

如果by后面跟的是一个表达式那么只会执行一次

Loop 和条件

loop

使用if, else 和 finally:

1
2
3
4
5
6
7
(loop repeat 10
      for x = (random 100)
      if (evenp x)
        collect x into evens
      else
        collect x into odds
      finally (return (values evens odds)))
1
2
(42 82 24 92 92)
(55 89 59 13 49)

如果要结合多个语句,那么if的body需要and关键字(and do, and count)

1
2
3
4
5
6
7
8
9
(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)))
1
2
3
4
5
6
7
8
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

用一个语句作为loop的开始(initially)

1
2
3
4
5
(loop initially
  (format t "~a " 'loop-begin)
      for x below 3
      do (format t "~a " x))
;;LOOP-BEGIN 0 1 2

用一个test(until,while)来结束循环

loop

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

循环命名 和 提前退出

loop

loop named foo 语法允许你创建一个能够提前退出的循环。使用return-form,即可退出已经命名的循环,甚至可以退出嵌套的循环。

1
2
3
4
5
6
7
(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

有的时候你想要提前退出,但是一定要执行一些语句,你可以使用loop-finish

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(loop for x from 0 to 100
      do (print x)
      when (>= x 3)
        return x
      finally (print :done))

;; 0
;; 1
;; 2
;; 3
;; 3

(loop for x from 0 to 100
      do (print x)
      when (>= x 3)
        do (loop-finish)
      finally (print :done)
              (return x))

;; 0
;; 1
;; 2
;; 3
;; :DONE
;; 3

Loop thereis never always

  • thereis
1
2
3
(loop for x in '(foo 2)
      thereis (numberp x))
T
  • never
    1
    2
    3
    
    (loop for x in '(foo 2)
          never (numberp x))
    NIL
    
  • always
    1
    2
    3
    
    (loop for x in '(foo 2)
          always (numberp x))
    NIL
    

他们和some,notany,every对应:

1
2
3
(some #'numberp '(foo 2))
(notany #'numberp '(foo 2))
(every #'numberp '(foo 2))

Count

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

Summation

1
2
(loop for i from 1 to 3 sum (* i i ))
;; 14

将求和的结果放入变量中

1
2
3
4
5
6
7
8
(loop for i from 1 to 3
      sum (* i i) into total
      do (print i)
      finally (print total))
1
2
3
14

Max and Min

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

解构, 对 list 进行dotted pairs aka 模式匹配

1
2
3
4
5
6
(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)

使用nil忽略

1
2
3
(loop for (a nil) in '((x 1) (y 2) (z 3))
      collect a)
;; (X Y Z)

两个两个的遍历

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