Let. Циклические предложения
ЛЕКЦИЯ 5.
LET. ЦИКЛИЧЕСКИЕ ПРЕДЛОЖЕНИЯ
Содержание
5.1 L E T
В том случае, когда используется вычисление последовательности форм, удобно бывает ввести локальные переменные, сохраняемые до окончания вычислений. Это делается с помощью предложения LET.
В общем виде LET записывается
(LET ((var1 знач1) (var2 знач2)...) форма1 форма2 ... формаN)
LET вычисляется следующим образом:
1. Локальные переменные var1, var2, ...varM связываются одновременно со знач1, знач2, ..., значМ.
2. Вычисляются последовательно аргументы форма1, форма2, формаN.
3. В качестве значения предложения принимается значение последнего аргумента (неявный PROGN).
4. После выхода из предложения связи переменных var1, var2, ...varM ликвидируются.
Предложение LET удобно использовать, когда надо временно сохранять промежуточные значения.
Пример.
Рассмотрим функцию rectangle, которая имеет один аргумент - список из двух элементов, задающих длину и ширину прямоугольника. Функция рассчитывает и печатает площадь периметр прямоугольника.
(defun rectangle (dim) (let ((len (car dim)) (wid (cadr dim))) (print (list 'area (* len wid))) (print (list 'perimeter (* (+ len wid) 2)))))) * (rectangle '(4 5)) |
Можно сначала рассчитать площадь, т.е. определить
(defun rectangle (dim) (let ((len (car dim)) (wid (cadr dim)) (area (* len wid)) (print ( 'area area)) (print (list 'perimeter (* (+ len wid))))))) Обращение * (rectangle '(4 5))
|
Надо использовать предложение LET* , в котором значение переменных задается последовательно.
(defun rectangle (dim) (let* ((len (car dim)) (wid (cadr dim)) (area (* len wid))) (print (list 'area area)) (print (list 'perimeter (* (+ len wid) 2))))))) |
5.2 Условный выход из функции: PROG RETURN
Встречаются ситуации, когда из тела функции, представленного последовательностью форм, требуется выйти, не доходя до последней формы. Это можно сделать используя предложения PROG RETURN, которые используются вместе.
Рассмотрим пример.
Необходимо написать функцию которая вводит два значения. Если это числа функция печатает их сумму и разность.
Если хотя бы одно не является числом, печатается nil.
* (defun s-d () (prog (x y); локальные переменные (print '(type number)) (setq x (read)) (and (not (numberp x)) (return nil)) (print '(type number)) (setq y (read)) (and (not (numberp y)) (return nil)) (print (+ x y)) (print (- x y)))) * (s-d) (type number)8 (type number) (1) nil |
Если return не встретился - результат prog будет nil .
* (s-d) (type number)8 (type number)1 9 7 nil |
5.3 Дополнительные функции печати
PRINT печатает значение аргумента без пробела и перевода на другую строку:
* (progn (print 1) (print 2) (print 3)) 123 |
"string" |
* "(+ 1 2)" "(+ 1 2)" |
PRINC печатает строки без "".
PRINC печатает аргумент без пробела и перевода строки
Пример
* (progn (setq x 4) (princ " x = ") (prin1 x) (princ " m ")) x = 4 m |
PRINC обеспечивает гибкий вывод.
TERPRI производит перевод строки. Как значение возвращает nil.
* (progn (setq x 4) (princ "xxx ") (terpri) (princ "xox ")) xxx xox " xox" |
5.4 Циклические предложения
Циклические вычисления в лиспе выполняются или с помощью итерационных (циклических) предложений
или рекурсивно.
Познакомимся вначале с циклическими предложениями
5.4.1 LOOP
Предложение LOOP реализует бесконечный цикл
(LOOP форма1 форма2 .....)
в которoм формы вычисляются до тех пор, пока
не встретится явный оператор завершения RETURN.
5.4.1. 1 Применение LOOP для численных итераций.
Определим функцию add-integer, которая будет брать один аргумент, являющийся положительным целым, и возвращает сумму всех чисел между 1 и этим числом.
1+2+3+4+ ... +N 1+2+3+4=10 * (add-integers 4) 10 (defun add-integers (last) (let (( count 1) (total 1)) (loop (cond (( equal count last) (return total))) (setq count (+ 1 count)) (setq total (+ total count))))) |
Например, 3 x 4 это 3 + 3 + 3 + 3 = 12 * (int-multiply 3 4) 12 (defun int-multiply (x y) (let ((result 0)( count 0)) (loop (cond (( equal count y) (return result))) (setq count (+ 1 count)) (setq result (+ result x))))) |
(defun < имя-функции > < список-параметров >
(let (< инициализация переменной индекса >
< инициализация переменной результата >)
(loop
(cond < проверка индекса на выход > (return результат))
< изменение переменной счетчика >
< другие действия в цикле,
включая изменение переменой результата >)))
Еще пример. Определим функцию factorial
* (factorial 5) 120 1 x 2 x 3 x 4 x 5 = 120 (defun factorial ( num ) (let ((counter 0)( product 1)) (loop (cond (( equal counter num) (return product))) (setq counter (+ 1 counter)) (setq product (* counter product ))))) |
( progn (setq x 0) (loop (if ( = 3 x) (return 't) (print x)) (setq x (+ 1 x)))) 0 1 2 t |
" Enter the next number: " перед каждым вводом.
* ( read-sum) Enter the next number: 15 Enter the next number: 30 Enter the next number: 45 Enter the next number: stop 90 (defun read-sum () (let ((input) (sum 0)) (loop (princ "Enter the next number:") (setq input (read)) (cond (( not (numberp input)) (return sum))) (setq sum (+ input sum))))) |
/p>
5.4.1. 2 Применение LOOP для итераций co списками.
Предположим, что нам необходима функция double-list, принимающая список чисел и возвращает новый список в котором каждое число удвоено.
* (double-list '(5 15 10 20)) (10 30 20 40) (defun double-list ( lis ) (let ((newlist nil)) (loop (cond (( null lis ) (return newlist))) (setq newlist (append newlist (list (* 2 (car lis))))) (setq lis (cdr lis ))))) |
list | newlist | |
Начальное состояние | (5 15 10 20) | () |
итерация 1 | (15 10 20) | (10) |
итерация 2 | (10 20) | (10 30) |
итерация 1 | (20) | (10 30 20) |
итерация 4 | () | (10 30 20 40) |
результат | (10 30 20 40) |
5.5 DO
Это самое общее циклическое предложение
Общая форма
( DO (( var1 знач1 шаг1) ( var2 знач2 шаг2)....)
( условие-окончания форма11 форма12...)
форма21
форма21 ...)
1) Вначале локальным переменным var1 ..varn присваиваются начальные значения знач1..значn. Переменным, для которых не заданы начальные значения присваивается nil.
2) Затем проверяется условие окончания, если оно выполняется вычисляются форма11, форма12... В качестве значения берется значение последней формы.
3) Если условие не выполняется, то вычисляются форма21, форма22...
4) На следующем цикле переменным vari присваиваются одновременно новые значения определяемые формами шагi и все повторяется.
Пример
* ( do (( x 1 ( + 1 x))) (( > x 10) ( print 'end)) ( print x)) |
В конце end.
Можно сравнить итерационное вычисление с LOOP и DO.
Напишем функцию list-abs, которая берет список чисел и возвращает список абсолютных величин этих чисел.
(defun list-abs (lis) (let ((newlist nil)) (loop (cond (( null lis ) (return (reverse newlist)))) (setq newlist (cons (abs (car lis)) newlist)) (setq lis (cdr lis ))))) * (list-abs '(-1 2 -4 5)) |
(defun list-abs (lis) (do ((oldlist lis (cdr oldlist)) (newlist nil (cons (abs (car oldlist)) newlist))) ((null oldlist) (reverse newlist))))) |
Может одновременно изменяться значения нескольких переменных
* ( do (( x 1 (+ 1 x)) ( y 1 (+ 2 y)) ( z 3)); значение не меняется (( > x 10) ( print 'end)) (princ " x=") ( prin1 x) (princ " y=") ( prin1 y) (princ " z=") ( prin1 z) (terpri)) |
* ( do (( x 1 (+ 1 x))) (( > x 10)) ( do (( y 1 (+ 2 y))) (( > y 4)) ( princ " x= ") ( prin1 x) ( princ " y= ") ( prin1 y) (terpri) )) |
5.5.1 Обработка списков c DO.
Напишем функцию, которая будет читать элементы с клавиатуры и объединять в список. Ввод будет закончен, когда будет введен последний элемент end
( defun appen-read () ( do (( x ( list ( read)) ( append x (list (read))))) (( equal (last x) '(end))); ???? '(end) (print x))) ( appen-read) |
5.6 DOTIMES
DOTIMES можно использовать вместо DO, если надо повторить вычисления заданное число раз.
Общая форма
(DOTIMES ( var num форма-return) ( форма-тело))
здесь var - переменная цикла,
num - форма определяющая число циклов,
форма - return - результат, который должен быть возвращен.
Прежде всего вычисляется num-форма, в результате получается целое число-count.
Затем var меняется от 0 до count (исключая count) и соответственно каждый раз вычисляется форма-тело.
Последним вычисляется форма-return.
Если форма-return отсутствует, возвращается nil.
Например,
* (dotimes ( x 3 ) ( print x)) 0 - автоматически 1 2 t * (let ((x nil)) (dotimes (n 5 x) (setq x (cons n x)))) ( 4 3 2 1 0) |