common-lisp
LOOP, wspólne makro Lisp do iteracji
Szukaj…
Pętle ograniczone
Możemy powtórzyć akcję kilka razy za pomocą 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)
Pętla nad sekwencjami
(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
Oto podsumowanie słów kluczowych
Słowo kluczowe | Typ sekwencji | Typ zmiennej |
---|---|---|
w | lista | element listy |
na | lista | trochę cdr listy |
przez | wektor | element wektora |
Pętle nad tabelami skrótów
(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))
Prosta forma PĘTLI
Prosty formularz LOOP bez specjalnych słów kluczowych:
(loop forms...)
Aby wyjść z pętli, możemy użyć (return <return value>)
`
Kilka przykładów:
(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!~%")))))
Pętle nad pakietami
(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))
Pętle arytmetyczne
(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
Restrukturyzacja w wyciągach FOR
Możemy niszczyć listy obiektów złożonych
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)
Możemy również zniszczyć samą listę
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)
Jest to przydatne, gdy chcemy iterować tylko niektóre elementy
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)
Używanie NIL
do zignorowania terminu:
(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
PĘTLA jako wyrażenie
W przeciwieństwie do pętli w prawie każdym innym obecnie używanym języku programowania, LOOP
w Common Lisp może być używany jako wyrażenie:
(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
powoduje, że LOOP
zwraca największą oszacowaną wartość. MINIMIZE
jest przeciwieństwem MAXIMIZE
.
(loop repeat 100
for x = (random 1000)
maximize x)
COUNT
mówi, ile razy wyrażenie zostało ocenione na wartość NIL
niż NIL
podczas pętli:
(loop repeat 100
for x = (random 1000)
count (evenp x))
LOOP
ma również równoważników some
, every
, a notany
funkcji:
(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))
... z wyjątkiem tego, że nie ograniczają się do iteracji sekwencji:
(loop for value = (read *standard-input* nil :eof)
until (eq value :eof)
never (stringp value))
Czasowniki generujące wartości LOOP
można również pisać z sufiksem -ing:
(loop repeat 100
for x = (random 1000)
minimizing x)
Możliwe jest również przechwycenie wartości generowanej przez te czasowniki do zmiennych (które są tworzone pośrednio przez makro LOOP
), dzięki czemu można wygenerować więcej niż jedną wartość naraz:
(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)))
Możesz mieć więcej niż jedną klauzulę collect
, count
itp., Która zbiera do tej samej wartości wyjściowej. Będą wykonywane sekwencyjnie.
Poniższe konwertuje listę asocjacji (której można użyć z assoc
) na listę właściwości (której można używać z getf
):
(loop for (key . value) in assoc-list
collect key
collect value)
Chociaż jest to lepszy styl:
(loop for (key . value) in assoc-list
append (list key value))
Warunkowe wykonywanie klauzul LOOP
LOOP
ma własną instrukcję IF
, która może kontrolować sposób wykonywania klauzul:
(loop repeat 1000
for x = (random 100)
if (evenp x)
collect x into evens
else
collect x into odds
finally (return (values evens odds)))
Łączenie wielu klauzul w treści JEŻELI wymaga specjalnej składni:
(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)))
Iteracja równoległa
W LOOP
dozwolonych jest wiele klauzul FOR
. Pętla kończy się, gdy kończy się pierwsza z tych klauzul:
(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))
Inne klauzule określające, czy pętla powinna być kontynuowana, można łączyć:
(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)
Ustal, która lista jest dłuższa, odcinając iterację, gdy tylko odpowiedź będzie znana:
(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)))
Numerowanie elementów listy:
(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))
Upewnij się, że wszystkie liczby na liście są parzyste, ale tylko dla pierwszych 100 pozycji:
(assert
(loop for number in list
repeat 100
always (evenp number)))
Iteracja zagnieżdżona
Specjalna składnia LOOP NAMED foo
umożliwia utworzenie pętli, z której można wcześniej wyjść. Wyjście jest wykonywane przy użyciu return-from
i może być używane z zagnieżdżonych pętli.
W poniższym przykładzie zastosowano zagnieżdżoną pętlę do wyszukiwania liczby zespolonej w tablicy 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))))
Klauzula RETURN a forma RETURN.
Wewnątrz LOOP
możesz użyć formularza Common Lisp (return)
w dowolnym wyrażeniu, co spowoduje, że formularz LOOP
natychmiast oceni wartość podaną do return
.
LOOP
ma również klauzulę return
, która działa prawie identycznie, jedyną różnicą jest to, że nie otaczasz jej nawiasami. Klauzula jest używana w DSL LOOP
, natomiast forma jest używana w wyrażeniach.
(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))
Rzecz w finally
musi być wyrażeniem, więc należy użyć formularza (return)
a nie klauzuli 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.
Pętla nad oknem listy
Kilka przykładów okna o rozmiarze 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