Цикл loop

Я уже рассказывал про циклы, но ничего не сказал про цикл loop. Я сделал это умышленно. Во-первых потому, что данный цикл довольно объёмен по параметрам и, статья получилась бы огромной. Во-вторых это не просто цикл, а язык написания циклов, который, с моей точки зрения, довольно продуман. 

Ключевые слова

Пройдёмся по ключевым словам этого мини-языка. Первый параметр управляет итерированием. Это параметр for и его синоним as. Этот параметр позволяет итерировать по:

  • Численные интервалы, вверх или вниз.
  • Отдельные элементы списка.
  • cons-ячейки, составляющие список.
  • Элементы вектора, включая подтипы, такие как строки и битовые векторы.
  • Пары хэш-таблицы.
  • Символы пакета.
  • Результаты повторных вычислений заданной формы.

В следующем примере мы используем параметр for два раза. Цикл завершится при завершении одного из параметров.

(loop
for item in list
for i from 1 to 10
do (something))

 

Подсчёт чего-либо тоже входит в ведение параметра for. Для этого существует три группы параметров. Первая группа это «откуда». Здесь можно использовать одно из ключевых слов from, downfrom или upfrom. За этим словом следует форма вычисляющее начальное значение.

Следующая группа «докуда» задает точку останова цикла и состоит из одного из предлогов to, upto, below, downto или above. За одним из этих предлогов следует форма, предоставляющая точку останова. С upto и downto цикл завершится без выполнения тела когда переменная перейдет точку останова. С below и above он завершится на итерацию ранее.

Последняя группа «по сколько» состоит из предлога by и формы, которая должна вычисляться в положительное число. Переменная будет изменяться (вверх или вниз, что определяется другими оборотами) на эту величину на каждой итерации, или на единицу, если оборот опущен.

По умолчанию цикл начинается с 0, увеличивается на 1 и будет выполняться, пока любое условие не остановит его. Любое другое условие, если таких условий нет, то цикл будет вечным. Второе важное замечание заключается в том, что loop макрос, поэтому некорректные условия не могут быть проверены. Вот пример:

(loop for from 20 to 10) ; не будет работать, потому что 20 > 10
(loop for from 20 downto 10) ; так будет
(loop for downfrom 20 to 10) ; и так

 

Если нужно просто цикл повторяющийся какое-то количество раз, то вместо for можно написать repeat:

(loop repeat 10)

Итерация по коллекциям

В случае итерации по спискам для for есть только 2 предлога — in и on. Эти два предлога можно закончить атрибутом by. Атрибут by задаёт функцию, с помощью которой следует продвигаться по списку. Для того чтобы итерировать по вектору используем across вместо in/on.

Немного иначе обстоит дело с итерацией по хэш-таблицам или пакетам. Для этого существуют свои атрибуты:

(loop for var being the things in hash-or-package ...)

 

В этом примере вместо things могут быть: hash-keys, hash-values для хэш таблиц; symbols, present-symbols и external-symbols для пакета.

Условная итерация

Этот вид итерации хорошо нам знаком, но он всё же отличается от простого перебора или повторений по значениям. Поэтому и синтаксис у него другой:

(loop for var = initial-value-form [ then step-form ] ...)

Если формы then нет, то initial-value-form будет вместо неё. Если нужно несколько переменных, то можно несколько for написать. Но если нужно вычислять эти переменные на основе других таких же, то вместо for будет and.

(loop repeat 5
for y = 1 then (+ x y)
for x = 0 then y
collect y)
; (1 1 2 4 8)

(loop repeat 5
for x = 0 then y
and y = 1 then (+ x y)
collect y)
; (1 1 2 3 5)

Думаю по разнице понятно, что происходит. Если нужны локальные переменные, которые косвенно учавствуют в итерировании, а не напрямую, то есть атрибут with.

К тому же цикл loop предоставляет три условных конструкции, и все они следуют этому базовому образцу:

conditional test-form loop-clause

Условный оператор conditional может быть if, when или unless. test-form — это любая обычная форма Lisp, а предложение loop-clause может быть предложением накопления значения (count, collect и так далее), предложением безусловного выполнения или другим предложением условного выполнения. Внутри loop if и when синонимы, разницы в их поведении нет. Все три условных предложения могут также принимать ветвь else, в которой за else следует другое предложение loop либо несколько предложений, объединенных and. Если условные предложения являются вложенными, множество предложений, связанных с внутренним условным предложением, может быть завершено с помощью слова end. end является необязательным. Пример посложнее и побогаче:

(loop for i from 1 to 100
	  if (evenp i)
	  minimize i into min-even and
	  maximize i into max-even and
	  unless (zerop (mod i 4))
	  sum i into even-not-fours-total
	  end
	  and sum i into even-total
	  else
	  minimize i into min-odd and
	  maximize i into max-odd and
	  when (zerop (mod i 5))
	  sum i into fives-total
	  end
	  and sum i into odd-total
	  do (update-analysis min-even
						  max-even
						  min-odd
						  max-odd
						  even-total
						  odd-total
						  fives-total
						  even-not-fours-total))

Деструктурирование переменных

Цикл loop может менять структуру данных. Сложное описание, но на примере довольно просто:

(loop for (a b) in '((1 2) (3 4) (5 6))
do (format t "a: ~a; b: ~a~%" a b))

(loop for (item . rest) on list
do (format t "~a" item)
when rest do (format t ", "))

 

Если второе значение нам ни к чему, то можно использовать nil вместо имени переменной

Накопление значения

Это одна из классных фишек цикла loop. Возможность так просто накапливать значения. Общая форма для этого такова:

verb form [ into var ]

Возможными глаголами являются collect, append, nconc, count, sum, maximize и minimize. Также доступны синонимы в форме причастий настоящего времени: collecting, appending, nconcing, counting, summing, maximizing и minimizing.

(defparameter *random* (loop repeat 100 collect (random 10000)))
(loop for i in *random*
counting (evenp i) into evens
counting (oddp i) into odds
summing i into total
maximizing i into max
minimizing i into min
finally (return (list min max total evens odds)))

Выполнение произвольного кода

Самым простым способом выполнения произвольного кода внутри тела цикла является использование предложения do. По сравнению с вышеописанными предложениями со всеми их предлогами и подвыражениями, do следует модели простоты по Йоде. Предложение do состоит из слова do (или doing), за которым следует одна или более форм Lisp, которые вычисляются при вычислении предложения do. Предложение do заканчивается закрывающей скобкой цикла loop или следующим ключевым словом loop.

(loop for i from 1 to 10 do (print i))

Предварительные вычисления и пост-обработка

Иногда нужно выполнить какие-то действия перед циклом, например объявить переменные или что-нибудь подобное. И если в других языках это отдельные конструкции. То при проектировании цикла loop решили, что лучше внести это внутрь цикла. Мне этот подход по душе. Для реализациии этой возможности предоставлено два ключевых слова, initially и finally, которые вводят код для запуска снаружи главного тела цикла.

Все формы initially комбинируются в единую вводную часть (prologue), которая запускается однократно непосредственно после инициализации всех локальных переменных цикла и перед его телом. Формы finally схожим образом комбинируются в заключительную часть (epilogue) и выполняются после последней итерации цикла. И вводная, и заключительная части могут ссылаться на локальные пемеренные цикла.

Критерии завершения

Бывают случаи, когда необходимо завершить цикл по какому-то условию. В си подобных языках для этого годится цикл while. Для loop есть такие ключевые слова-предложений завершения while, until, always, never и thereis. Все они следуют одинаковому образцу:

loop-keyword test-form

Все эти предложения вычисляют форму test-form на каждой итерации и на основе возвращаемого ей значения принимают решение, завершить ли выполнение цикла.

Ключевые слова loop while и until предоставляют «мягкие» предложения завершения. Если они решают завершить цикл, управление передается в заключительную часть, пропуская оставшуюся часть тела цикла.

Другая форма мягкго завершения предоставляется макросом LOOP-FINISH. Это обычная форма Lisp, не предложение loop, поэтому она может использоваться в любом месте внутри форм Lisp предложения do. LOOP-FINISH также приводит к немедленному переходу к заключительной части, и может быть полезен, когда решение о прерывании цикла не может быть легко умещено в единственную форму, могущую использоваться в предложениях while или until.
Остальные три предложения, always, never и thereis, останавливают цикл гораздо более жестко: они приводят к немедленному возврату из цикла, пропуская не только все последующие предложения loop, но и заключительную часть. Предложения always и never возвращают только булевы значения, поэтому они наиболее полезны в случае, если вам нужно использовать выражение цикла как часть предиката. Пример — проверка чётности:

(if (loop for n in numbers always (evenp n))
(print "All numbers even."))

Цикл loop: 2 комментария

  1. А как должен выглядеть полный пример кода для запуска?
    > (loop
    for item in list
    for i from 1 to 10
    do (something))

    *** — LET: variable LIST has no value

    1. здесь или можно опустить строчку с for item in list, или объявить эту переменную. И написать что-то вместо (something) — код который должен быть выполнен 10 раз

      например так:
      (loop for item in ‘(1 2 3) for i from 1 to 10
      do (format t «item: ~item; i: ~i~%» item i))

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.