common-lisp
LOOP、反復のためのCommon Lispマクロ
サーチ…
束縛ループ
私たちは繰り返しを使ってrepeat
回も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度も何度もrepeat
ます
CL-USER> (loop repeat 10 do (format t "Hello!~%")) Hello! Hello! Hello! Hello! Hello! Hello! Hello! Hello! Hello! Hello! NIL CL-USER> (loop repeat 10 collect (random 50)) (28 46 44 31 5 33 43 35 37 4)
シーケンスをループする
(loop for i in '(one two three four five six)
do (print i))
(loop for i in '(one two three four five six) by #'cddr
do (print i)) ;prints ONE THREE FIVE
(loop for i on '(a b c d e f g)
do (print (length i))) ;prints 7 6 5 4 3 2 1
(loop for i on '(a b c d e f g) by #'cddr
do (print (length i))) ;prints 7 5 3 1
(loop for i on '(a b c)
do (print i)) ;prints (a b c) (b c) (c)
(loop for i across #(1 2 3 4 5 6)
do (print i)) ; prints 1 2 3 4 5 6
(loop for i across "foo"
do (print i)) ; prints #\f #\o #\o
(loop for element across "foo"
for i from 0
do (format t "~a ~a~%" i element)) ; prints 0 f\n1 o\n1 o
ここにキーワードの概要があります
キーワード | シーケンスタイプ | 変数型 |
---|---|---|
に | リスト | リストの要素 |
に | リスト | リストの一部 |
横断する | ベクター | ベクトルの要素 |
ハッシュテーブルのルーピング
(defvar *ht* (make-hash-table))
(loop for (sym num) on
'(one 1 two 2 three 3 four 4 five 5 six 6 seven 7 eight 8 nine 9 ten 10)
by #'cddr
do (setf (gethash sym *ht*) num))
(loop for k being each hash-key of *ht*
do (print k)) ; iterate over the keys
(loop for k being the hash-keys in *ht* using (hash-value v)
do (format t "~a=>~a~%" k v))
(loop for v being the hash-value in *ht*
do (print v))
(loop for v being each hash-values of *ht* using (hash-key k)
do (format t "~a=>~a~%" k v))
シンプルルーンフォーム
特別なキーワードのない単純なLOOPフォーム:
(loop forms...)
ループを抜け出すには、 (return <return value>)
`
いくつかの例:
(loop (format t "Hello~%")) ; prints "Hello" forever
(loop (print (eval (read)))) ; your very own REPL
(loop (let ((r (read)))
(typecase r
(number (return (print (* r r))))
(otherwise (format t "Not a number!~%")))))
パッケージをループする
(loop for s being the symbols in 'cl
do (print s))
(loop for s being the present-symbols in :cl
do (print s))
(loop for s being the external-symbols in (find-package "COMMON LISP")
do (print s))
(loop for s being each external-symbols of "COMMON LISP"
do (print s))
(loop for s being each external-symbol in pack ;pack is a variable containing a package
do (print s))
算術ループ
(loop for i from 0 to 10
do (print i)) ; prints 0 1 2 3 4 5 6 7 8 9 10
(loop for i from 0 below 10
do (print i)) ; prints 0 1 2 3 4 5 6 7 8 9 10
(loop for i from 10 above 0
do (print i)) ; prints 10 9 8 7 6 5 4 3 2 1
(loop for i from 10 to 0
do (print i)) ; prints nothing
(loop for i from 10 downto 0
do (print i)) ; prints 10 9 8 7 6 5 4 3 2 1 0
(loop for i downfrom 10 to 0
do (print i)) ; same as above
(loop for i from 1 to 100 by 10
do (print i)) ; prints 1 11 21 31 41 51 61 71 81 91
(loop for i from 100 downto 0 by 10
do (print i)) ; prints 100 90 80 70 60 50 40 30 20 10 0
(loop for i from 1 to 10 by (1+ (random 3))
do (print i)) ; note that (random 3) is evaluated only once
(let ((step (random 3)))
(loop for i from 1 to 10 by (+ step 1)
do (print i))) ; equivalent to the above
(loop for i from 1 to 10
for j from 11 by 11
do (format t "~2d ~3d~%" i j)) ;prints 1 11\n2 22\n...10 110
FOR文での破壊
複合オブジェクトのリストを削除することができます
CL-USER> (loop for (a . b) in '((1 . 2) (3 . 4) (5 . 6)) collect a)
(1 3 5)
CL-USER> (loop for (a . b) in '((1 . 2) (3 . 4) (5 . 6)) collect b)
(2 4 6)
CL-USER> (loop for (a b c) in '((1 2 3) (4 5 6) (7 8 9) (10 11 12)) collect b)
(2 5 8 11)
リスト自体を分解することもできます
CL-USER> (loop for (a . b) on '(1 2 3 4 5 6) collect a)
(1 2 3 4 5 6)
CL-USER> (loop for (a . b) on '(1 2 3 4 5 6) collect b)
((2 3 4 5 6) (3 4 5 6) (4 5 6) (5 6) (6) NIL)
これは、特定の要素だけを反復処理する場合に便利です
CL-USER> (loop for (a . b) on '(1 2 3 4 5 6) by #'cddr collect a)
(1 3 5)
CL-USER> (loop for (a . b) on '(1 2 3 4 5 6) by #'cdddr collect a)
(1 4)
NIL
を使用して用語を無視する:
(loop for (a nil . b) in '((1 2 . 3) (4 5 . 6) (7 8 . 9))
collect (list a b)) ;=> ((1 3) (4 6) (7 9))
(loop for (a b) in '((1 2) (3 4) (5 6)) ;(a b) == (a b . nil)
collect (+ a b)) ;=> (3 7 11)
; iterating over a window in a list
(loop for (pre x post) on '(1 2 3 4 5 3 2 1 2 3 4)
for nth from 1
while (and x post) ; checks that we have three elements of the list
if (and (<= post x) (<= pre x)) collect (list :max x nth)
if (and (>= post x) (>= pre x)) collect (list :min x nth))
; The above collects local minima/maxima
式としてのLOOP
現在使用されているほとんどすべてのプログラミング言語のループとは異なり、Common LispのLOOP
は式として使用できます。
(let ((doubled (loop for x from 1 to 10
collect (* 2 x))))
doubled) ;; ==> (2 4 6 8 10 12 14 16 18 20)
(loop for x from 1 to 10 sum x)
MAXIMIZE
と、 LOOP
は評価された最大値を返します。 MINIMIZE
はMAXIMIZE
の反対です。
(loop repeat 100
for x = (random 1000)
maximize x)
COUNT
は、式がループ中に非NIL
評価された回数を示します。
(loop repeat 100
for x = (random 1000)
count (evenp x))
LOOP
は、 some
、 every
、 notany
関数と同等のものもあります:
(loop for ch across "foobar"
thereis (eq ch #\a))
(loop for x in '(a b c d e f 1)
always (symbolp x))
(loop for x in '(1 3 5 7)
never (evenp x))
...シーケンスを反復することに限定されているわけではありません。
(loop for value = (read *standard-input* nil :eof)
until (eq value :eof)
never (stringp value))
LOOP
値生成動詞には、-ing接尾辞を付けることもできます。
(loop repeat 100
for x = (random 1000)
minimizing x)
これらの動詞によって生成された値を変数( LOOP
マクロによって暗黙的に作成される)に取り込むこともできます。これにより、一度に複数の値を生成することができます。
(loop repeat 100
for x = (random 1000)
maximizing x into biggest
minimizing x into smallest
summing x into total
collecting x into xs
finally (return (values biggest smallest total xs)))
同じ出力値にcollect
、 count
などの句を複数持つことができます。それらは順番に実行されます。
以下は、アソシエーションリスト( assoc
と一緒に使うことができる)をプロパティリスト( getf
使うことができる)に変換します:
(loop for (key . value) in assoc-list
collect key
collect value)
これはより良いスタイルですが、
(loop for (key . value) in assoc-list
append (list key value))
条件付きで実行するLOOP句
LOOP
は、句の実行方法を制御できる独自のIF
文があります。
(loop repeat 1000
for x = (random 100)
if (evenp x)
collect x into evens
else
collect x into odds
finally (return (values evens odds)))
IF本体で複数の句を組み合わせるには、特殊な構文が必要です。
(loop repeat 1000
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)))
並列反復
LOOP
では複数のFOR
句を使用できます。ループは、これらの句の最初のものが終了すると終了します。
(loop for a in '(1 2 3 4 5)
for b in '(a b c)
collect (list a b))
;; Evaluates to: ((1 a) (2 b) (3 c))
ループを継続するかどうかを決定する他の句を組み合わせることができます。
(loop for a in '(1 2 3 4 5 6 7)
while (< a 4)
collect a)
;; Evaluates to: (1 2 3)
(loop for a in '(1 2 3 4 5 6 7)
while (< a 4)
repeat 1
collect a)
;; Evaluates to: (1)
答えがわかるとすぐに、どのリストが長いかを判断し、反復をカットします。
(defun longerp (list-1 list-2)
(loop for cdr1 on list-1
for cdr2 on list-2
if (null cdr1) return nil
else if (null cdr2) return t
finally (return nil)))
リストの要素の番号付け:
(loop for item in '(a b c d e f g)
for x from 1
collect (cons x item))
;; Returns ((1 . a) (2 . b) (3 . c) (4 . d) (5 . e) (6 . f) (7 . g))
リストのすべての数字が偶数であることを確認しますが、最初の100項目のみを確認してください。
(assert
(loop for number in list
repeat 100
always (evenp number)))
ネストされた反復
特別なLOOP NAMED foo
構文を使用すると、早期に終了できるループを作成できます。出口はreturn-from
を使用して実行され、ネストされたループ内から使用できます。
次の例では、ネストされたループを使用して、2D配列内の複素数を検索します。
(loop named top
for x from 0 below (array-dimension *array* 1)
do (loop for y from 0 below (array-dimension *array* 0))
for n = (aref *array* y x)
when (complexp n)
do (return-from top (values n x y))))
RETURN句対RETURN形式です。
LOOP
内では、任意の式でCommon Lisp (return)
形式を使用することができます。これにより、 LOOP
形式はすぐにreturn
値として評価されます。
LOOP
には、ほぼ同じように動作するreturn
節もありますが、括弧で囲まないと唯一の違いがあります。この節はLOOP
のDSL内で使用され、フォームは式内で使用されます。
(loop for x in list
do (if (listp x) ;; Non-barewords after DO are expressions
(return :x-has-a-list)))
;; Here, both the IF and the RETURN are clauses
(loop for x in list
if (listp x) return :x-has-a-list)
;; Evaluate the RETURN expression and assign it to X...
;; except RETURN jumps out of the loop before the assignment
;; happens.
(loop for x = (return :nothing-else-happens)
do (print :this-doesnt-print))
finally
は式でなければならないので、 return
句ではなく、 (return)
形式を使用する必要があります。
(loop for n from 1 to 100
when (evenp n) collect n into evens
else collect n into odds
finally return (values evens odds)) ;; ERROR!
(loop for n from 1 to 100
when (evenp n) collect n into evens
else collect n into odds
finally (return (values evens odds))) ;; Correct usage.
リストのウィンドウをループする
サイズ3のウィンドウのいくつかの例:
;; Naïve attempt:
(loop for (first second third) on '(1 2 3 4 5)
do (print (* first second third)))
;; prints 6 24 60 then Errors on (* 4 5 NIL)
;; We will try again and put our attempt into a function
(defun loop-3-window1 (function list)
(loop for (first second third) on list
while (and second third)
do (funcall function first second third)))
(loop-3-window1 (lambda (a b c) (print (* a b c))) '(1 2 3 4 5))
;; prints 6 24 60 and returns NIL
(loop-3-window1 (lambda (a b c) (print (list a b c))) '(a b c d nil nil e f))
;; prints (a b c) (b c d) then returns NIL
;; A second attempt
(defun loop-3-window2 (function list)
(loop for x on list
while (nthcdr 2 x) ;checks if there are at least 3 elements
for (first second third) = x
do (funcall function first second third)))
(loop-3-window2 (lambda (a b c) (print (list a b c))) '(a b c d nil nil e f))
;; prints (a b c) (b c d) (c d nil) (c nil nil) (nil nil e) (nil e f)
;; A (possibly) more efficient function:
(defun loop-3-window2 (function list)
(let ((f0 (pop list))
(s0 (pop list)))
(loop for first = f0 then second
and second = s0 then third
and third in list
do (funcall function first second third))))
;; A more general function:
(defun loop-n-window (n function list)
(loop for x on list
while (nthcdr (1- n) x)
do (apply function (subseq x 0 n))))
;; With potentially efficient implementation:
(define-compiler-macro loop-n-window (n function list &whole w)
(if (typep n '(integer 1 #.call-arguments-limit))
(let ((vars (loop repeat n collect (gensym)))
(vars0 (loop repeat (1- n) collect (gensym)))
(lst (gensym)))
`(let ((,lst ,list))
(let ,(loop for v in vars0 collect `(,v (pop ,lst)))
(loop for
,@(loop for v0 in vars0 for (v vn) on vars
collect v collect '= collect v0 collect 'then collect vn
collect 'and)
,(car (last vars)) in ,lst
do ,(if (and (consp function) (eq 'function (car function))
w