プログラミング原人の進化ログ

プログラミング原人の進化論

オレ プログラミング ベンキョウ スル。マナンダ コト カク。

【Common Lisp】loopマクロの使い方まとめ

Common Lispでループ処理に使われるloopマクロの使い方です。網羅的にまとめた資料が少ないので、備忘録的にまとめておこうと思います。
loopマクロには多くの機能があります。なので、最初から全てを抑える必要はないと思います。

この記事の内容

loopマクロの機能の簡単な使い方の説明と使用例をひたすら列挙します。

紹介する機能一覧

loopマクロでは様々な節を使って、色々な機能を実現します。たくさんあってごちゃごちゃするので、とりあえずどんなものがあるのかだけ最初に書いておきます。

基本

loopコマンドを使ってループする際の基本的な機能を提供する節

  • do
  • for
  • while
  • until
  • repeat
  • return
  • initially, finally

範囲指定

ループする範囲を指定する節

  • for, as
  • in
  • on
  • across
  • by
  • from, to
  • upfrom, downfrom
  • upto, downto

条件分岐

ループ内の処理を条件によって分岐する節

  • if, when
  • unless
  • and
  • else
  • end

計算結果の集積

結果を集めてリストなどの形で返す節

  • collect
  • count
  • sum
  • minimize
  • maximize
  • append, nconc
  • into

その他

  • with
  • named
  • return-from

各節の説明と例

では、上の節の機能と具体例を書いていきます。

基本

for, as

forループ。ループ変数を指定する。

CL-USER> (loop for i
               below 5
               collect i)
(0 1 2 3 4)
CL-USER> (loop as i
               below 5
               collect i)
(0 1 2 3 4)

複数のループ変数を指定する場合:

CL-USER> (loop for i below 10
               for j below 20
               collect (+ i j))
(0 2 4 6 8 10 12 14 16 18)

このように、どちらかのループ範囲が尽きるまでループを行う。

ループをネストさせる:

CL-USER> (loop for i below 10
               collect (loop for j below 10
                             collect (cons i j)))
(((0 . 0) (0 . 1) (0 . 2) (0 . 3) (0 . 4) (0 . 5) (0 . 6) (0 . 7) (0 . 8)
  (0 . 9))
 ((1 . 0) (1 . 1) (1 . 2) (1 . 3) (1 . 4) (1 . 5) (1 . 6) (1 . 7) (1 . 8)
  (1 . 9))
 ((2 . 0) (2 . 1) (2 . 2) (2 . 3) (2 . 4) (2 . 5) (2 . 6) (2 . 7) (2 . 8)
  (2 . 9))
 ((3 . 0) (3 . 1) (3 . 2) (3 . 3) (3 . 4) (3 . 5) (3 . 6) (3 . 7) (3 . 8)
  (3 . 9))
 ((4 . 0) (4 . 1) (4 . 2) (4 . 3) (4 . 4) (4 . 5) (4 . 6) (4 . 7) (4 . 8)
  (4 . 9))
 ((5 . 0) (5 . 1) (5 . 2) (5 . 3) (5 . 4) (5 . 5) (5 . 6) (5 . 7) (5 . 8)
  (5 . 9))
 ((6 . 0) (6 . 1) (6 . 2) (6 . 3) (6 . 4) (6 . 5) (6 . 6) (6 . 7) (6 . 8)
  (6 . 9))
 ((7 . 0) (7 . 1) (7 . 2) (7 . 3) (7 . 4) (7 . 5) (7 . 6) (7 . 7) (7 . 8)
  (7 . 9))
 ((8 . 0) (8 . 1) (8 . 2) (8 . 3) (8 . 4) (8 . 5) (8 . 6) (8 . 7) (8 . 8)
  (8 . 9))
 ((9 . 0) (9 . 1) (9 . 2) (9 . 3) (9 . 4) (9 . 5) (9 . 6) (9 . 7) (9 . 8)
  (9 . 9)))

上の例はi, jによる2重ループとなっている。

while

whileループ。条件が偽になるまで繰り返す。

CL-USER> (loop for i from 0
               while (< i 3)
               do (print i))

0 
1 
2 
NIL

until

条件が真になるまで繰り返す。

CL-USER> (loop for i from 0
               until (> i 3)
               do (print i))

0 
1 
2 
3 
NIL

repeat

繰り返し回数を指定する。

CL-USER> (loop repeat 5
               do (print "five times"))

"five times" 
"five times" 
"five times" 
"five times" 
"five times" 
NIL

do

do以下に記述する任意の式を実行する。なお、暗黙のprognにより、複数の式を続けて記述できる。

CL-USER> (loop for i
               below 3
               do (fresh-line)
                  (princ "number is ")
                  (print i))
number is 
0 
number is 
1 
number is 
2 
NIL

return

値を返し、ループを抜ける。

CL-USER> (loop for i below 10
               when (= i 5)
                 return 'return
               do (print i))

0 
1 
2 
3 
4 
RETURN

initially, finally

それぞれ、ループ直前・直後の処理を記述する。複数の処理を続けて書ける。

CL-USER> (loop
           initially
              (print 'init-process)
              (print 'loop-begin)
              for i below 3
           do (print i)
              finally
                 (print 'loop-end)
                 (print 'final-process))

INIT-PROCESS 
LOOP-BEGIN 
0 
1 
2 
LOOP-END 
FINAL-PROCESS 
NIL

範囲指定

in

ループする範囲をリストで指定できる。

CL-USER> (loop for i
               in '(1 3 5 7 9)
               collect i)
(1 3 5 7 9)

on

inと似ているが、リストを頭から一つづつ削りながらループする。

CL-USER> (loop as i on '(1 3 5)
               do (print i))

(1 3 5) 
(3 5) 
(5) 
NIL

across

inと似ているが、リストではなく配列で範囲を指定する。

CL-USER> (loop for i 
               across #(1 3 5)
               do (print i))

1 
3 
5 
NIL

by

ループ変数を指定した間隔で増やしながらループできる。

CL-USER> (loop for i
               from 1 to 10 by 2
               collect i)
(1 3 5 7 9)

from, to

from m to n の形で、ループ変数がnからmまでループする。

CL-USER> (loop for i
               from 1 to 10
               collect i)
(1 2 3 4 5 6 7 8 9 10)

upfrom, downfrom

upfromはfromと同じ?downfromはループ関数をループごとに減らす時に使う。

CL-USER> (loop for i
               upfrom 1 to 10
               collect i)
(1 2 3 4 5 6 7 8 9 10)
CL-USER> (loop for i
               downfrom 5 to 1
                        collect i)
(5 4 3 2 1)

upto, downto

uptoはtoと同じ?downtoはループ変数を減らしながらループする時に使う

CL-USER> (loop for i
               from 1 upto 5
               collect i)
(1 2 3 4 5)
CL-USER> (loop for i
               from 10 downto 1
               collect i)
(10 9 8 7 6 5 4 3 2 1)

条件分岐

if, when

条件が真となる場合のみ、when直下の処理を実行する。

CL-USER> (loop for i
               from 1 to 10
               when (oddp i)
                 collect i)
(1 3 5 7 9)

unless

条件が偽となる場合のみ、unless直下の処理を実行する。

CL-USER> (loop for i below 4
               unless (oddp i)
                 do (print i))

0 
2 
NIL

else

ifやwhenで条件が偽の処理を記述する。

CL-USER> (loop for i below 5
               if (oddp i)
                 do (print i)
                    else
                      do (print "even"))

"even" 
1 
"even" 
3 
"even" 
NIL

and

whenやunlessで実行する処理を複数書きたい時に使う。

CL-USER> (loop for i below 5
               when (oddp i)
                 do (fresh-line) 
                 and 
                   do (princ "number is ") 
                   and 
                     do (princ i))
                        
number is 1
number is 3
NIL

end

複数の節の終わりを表現する。

CL-USER> (loop for i below 4
               when (oddp i)
                 do (print i)
                    end
               do (print "yup"))

"yup" 
1 
"yup" 
"yup" 
3 
"yup" 
NIL

計算結果の集積

collect

ループごとに指定した値を要素とするリストを作る。

CL-USER> (loop for i
               below 5
               collect i)
(0 1 2 3 4)

count

ループ変数の個数を数える。

CL-USER> (loop for i
               below 5
               when (oddp i)
                 count i)
2

sum

総和を計算する。

CL-USER> (loop for i
               below 5
               sum (+ i 1))
15

minimize

最小の値を見つける。

CL-USER> (loop for i
               in '(3 2 1 2 3)
               minimize i)
1

maximize

最大の値を見つける。

CL-USER> (loop for i
               in '(3 2 1 2 3)
               maximize i)
3

append, nconc

リストを繋げてリストにする。

CL-USER> (loop for i
               below 5
               append (list 'z i))
(Z 0 Z 1 Z 2 Z 3 Z 4)
CL-USER> (loop for i
               below 5
               nconc (list 'z i))
(Z 0 Z 1 Z 2 Z 3 Z 4)

into

ローカル変数を作り、集計した値を格納する。

CL-USER> (loop for i
               in '(3 8 73 4 -5)
               minimize i
               into lowest
               maximize i
               into biggest
               finally
                  (return (cons lowest biggest)))
(-5 . 73)

その他

with

ローカル変数を作成する。

CL-USER> (loop with x = (+ 1 2)
               repeat 5
               do (print x))

3 
3 
3 
3 
3 
NIL

named, return-from

namedはループに名前をつける。return-fromは指定した名前のループから抜ける。

CL-USER> (loop named outer
               for i below 10
               do
                  (progn
                    (print "outer")
                    (loop named inner
                          for x below i
                          do (print "**inner")
                             when (= x 2)
                               do
                                  (return-from outer
                                    'kicked-out-all-the-way))))
                           

"outer" 
"outer" 
"**inner" 
"outer" 
"**inner" 
"**inner" 
"outer" 
"**inner" 
"**inner" 
"**inner" 
KICKED-OUT-ALL-THE-WAY

まとめ

ひたすらloopの機能を羅列してきました。これでも多すぎ...と感じるのですが、まだ他にもコマンドがあります。ただ、実際に使うのはこれくらいなんじゃないかな?と思ってます。また書きたくなったら随時付け足していきます。

参考

コンラッドバルスキ(2013)「Land of Lisp」 川合史郎訳 オライリー・ジャパン