common-lisp
LOOP, общий макрос Lisp для итерации
Поиск…
Ограниченные петли
Мы можем повторить действие несколько раз, используя 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
Вот краткое описание ключевых слов
Ключевое слово | Тип последовательности | Переменный тип |
---|---|---|
в | список | элемент списка |
на | список | некоторый cdr списка |
через | вектор | элемент вектора |
Пересечение таблиц Hash
(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 без специальных ключевых слов:
(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 как выражение
В отличие от циклов почти на любом другом используемом сегодня языке программирования, LOOP
в Common Lisp может использоваться как выражение:
(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
, которое работает почти одинаково, с той лишь разницей, что вы не окружаете его круглыми скобками. Предложение используется в DSL LOOP
, в то время как форма используется в выражениях.
(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