Регистрация | Войти
Lisp — программируемый язык программирования

Циклы

Виды циклов

Бывают следующие виды циклов:

  • специализированные стандартные циклы (dolist - обойти список, dotimes - от нуля до N-1, do-all-symbols, do-external-symbols, do-symbols - обходить символы в пакетах)
  • общий стандартный цикл (do, do*)
  • простая форма loop (безконечный цикл с выходом по return)
  • сложная форма loop (подъязык для реализации сложных циклов)
  • iterate, хотя и не входит в стандарт, в целом является довольно удачным аналогом loop
  • "старомодные" циклы, реализованные с помощью tagbody и go

Некоторые специализированные циклы не существуют в виде do..., например, нет do-hash-table. На этот случай есть maphash. Для обхода размерностей массива можно использовать сочетание dolist и dotimes, все пакеты можно обойти с помощью dolist по результату list-all-packages и т.д.

Сложная форма loop

введение

Один из самых ценных и одновременно наименее документированных операторов в стандарте Коммон Лисп. Ценен он тем, что позволяет короче и яснее использовать функции высшего порядка (mapping) и рекурсивные операции. Также он позволяет использовать в Лиспе конструкции, более удобные для тех, кто привык работать с более традиционными языками.

Макрос Loop отличается от других операторов Лиспа, и больше напоминает конструкции Си или Паскаля. То есть, для чтения loop-выражений вам потребуется мыслить не только на языке Лиспа, но и на Паскале.

Loop-выражение может содержать выражения, определяющие итерируемые переменные; выражения, завершающие цикл (условно или безусловно); выражения, определяющие последовательность действий на каждой итерации; действия после завершения последней итерации. Кроме того, loop-выражения, как и полагается функциональной парадигме, возвращает какое-то значение. Редко когда приходится использовать все эти возможности, но оных позволяет их эффективно комбинировать.

Код, представленный в оригинале этого руководства, взят из Tutorial for the Common Lisp Loop Macro и использовался там по разрешению Peter Karp. 1)

примеры

Итерация по списку, печатаем каждый элемент:

(loop for x in '(a b c d e)
    do (print x)
)

A
B
C
D
E
NIL

Итерация параллельно по двум спискам, собираем результат в ассоциированный список:

(loop for x in '(a b c d e)
    for y in '(1 2 3 4 5)
    collect (cons x y)
)


((A . 1) (B . 2) (C . 3) (D . 4) (E . 5))

Итерация с использованием счетчика. Возвращает список из элементов, полученных в результате каждой итерации. 2)

(loop for x from1 to 5
    for y = (* x 2)
    collect y
)


(2 4 6 8 10)

Итерация с проходом по списку, параллельно с итерацией по счетчику. Окончание итерации по счетчику не определяется длиной списка. В теле loop-выражения используется условный оператор:

(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:

(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

Пример условно-досрочного завершения цикла:

(loop for x in '(a b c d e 1 2 3 4)
    until (numberp x)
    collect (list x 'foo)
)


((A FOO) (B FOO) (C FOO) (D FOO) (E FOO))

Также для организации условного завершения может использоваться "while":

(loop for x from 1
    for y = (* x 10)
    while (< y 100)

    do (print (* x 5))

    collect y
)


5
10
15
20
25
30
35
40
45
(10 20 30 40 50 60 70 80 90)

Loop-выражения могут быть вложены друг в друга:

(loop for x from 1 to 10
    collect (loop for y from 1 to x
                collect y
)
)


((1) (1 2) (1 2 3) (1 2 3 4) (1 2 3 4 5) (1 2 3 4 5 6) (1 2 3 4 5 6 7)
 (1 2 3 4 5 6 7 8) (1 2 3 4 5 6 7 8 9) (1 2 3 4 5 6 7 8 9 10)
)

На месте итерируемой переменной может стоять несколько переменных в виде списка. 3)

(loop for (a b) in '((x 1) (y 2) (z 3))
    collect (list b a)
)


((1 X) (2 Y) (3 Z))

Ключевое слово "return" (возврат) обеспечивает немедленный выход из цикла и (по желанию) возвращает значение, даваемое ему в виде первого аргумента:

(let ((s "alpha45"))
   (loop for i from 0 below (length s)
         for ch = (char s i)
         when (find ch "0123456789" :test #'eql)
         return ch
)
)


#\4

Также есть рад ключевых слов, позволяющих сократить запись loop-выражения с использованием when/return:

(loop for x in '(foo 2)
   thereis (numberp x)
)

T

(loop for x in '(foo 2)
    never (numberp x)
)

NIL

(loop for x in '(foo 2)
    always (numberp x)
)

NIL

Хотите еще?

Тут есть еще некоторое количество примеров, посвященных различным применениям loop. Если вы захотите ознакомится с первоисточником, то загляните в HyperSpec и CLTL.

Библиотеки

Iterate - еще одна возможность организовывать циклы, если вас не устраивает loop и другие стандартные возможности Common Lisp.

1)Надеемся, что он будет не против его размещения в настоящем переводе.
2)Одна из самых приятных возможностей loop
3)В данном примере эта возможность используется исключительно для демонстрации возможностей loop, т.к. проще это сделать, например, через mapcar и reverse
@2009-2013 lisper.ru