Функциональное программирование

       

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))
(area 20)
(perimetr 18)
(perimetr 18)

Можно сначала рассчитать площадь, т.е. определить

(defun rectangle (dim)
(let ((len (car dim)) (wid (cadr dim))
(area (* len wid))
(print ( 'area area))
(print (list 'perimeter (* (+ len wid)))))))

Обращение

* (rectangle '(4 5))


даст ошибку, т.к значение area неопределено.


Надо использовать предложение 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.

Если return не встретился - результат prog будет nil .
* (s-d)
(type number)8
(type number)1
9
7
nil

Если локальных переменных нет записывается (prog ()...)



5.3 Дополнительные функции печати



PRINT печатает значение аргумента без пробела и перевода на другую строку:
* (progn (print 1) (print 2) (print 3))
123

СТРОКИ - последовательность знаков заключенная в кавычки.
"string"

СТРОКА - специальный тип данных в лиспе. Это атом, но не может быть переменной. Как у числа значение строки сама строка.
* "(+ 1 2)"
"(+ 1 2)"

Строки удобно использовать для вывода с помощью оператора PRINC.

PRINC печатает строки без "".

PRINC печатает аргумент без пробела и перевода строки

Пример

* (progn (setq x 4) (princ " x = ")
(prin1 x) (princ " m "))
x = 4 m

" 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)))))

Еще один пример численной итерационной функции. Определим функцию выполняющую умножение двух целых чисел через сложение. Т.е. умножение x на y выполняется сложением x с самим собой y раз.
Например,

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))

То же, только через DO

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

Если есть необходимость можно использовать DO* аналогично LET*.



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)




Содержание раздела