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

       

Let. Циклические предложения


ЛЕКЦИЯ 5.

LET. ЦИКЛИЧЕСКИЕ ПРЕДЛОЖЕНИЯ


Содержание

Let. Циклические предложения

Let. Циклические предложения

Let. Циклические предложения

Let. Циклические предложения

Let. Циклические предложения

Let. Циклические предложения

Let. Циклические предложения

Let. Циклические предложения

Let. Циклические предложения

Let. Циклические предложения

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, которая имеет один аргумент - список из двух элементов, задающих длину и ширину прямоугольника. Функция рассчитывает и печатает площадь периметр прямоугольника.

Let. Циклические предложения (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)

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

Let. Циклические предложения (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* , в котором значение переменных задается последовательно.

Let. Циклические предложения (defun rectangle (dim)
(let* ((len (car dim)) (wid (cadr dim))
(area (* len wid)))
(print (list 'area area))
(print (list 'perimeter (* (+ len wid) 2)))))))

Let. Циклические предложения

5.2 Условный выход из функции: PROG RETURN

Встречаются ситуации, когда из тела функции, представленного последовательностью форм, требуется выйти, не доходя до последней формы. Это можно сделать используя предложения PROG RETURN, которые используются вместе.


Рассмотрим пример.

Необходимо написать функцию которая вводит два значения. Если это числа функция печатает их сумму и разность.

Если хотя бы одно не является числом, печатается nil.
Let. Циклические предложения * (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 .
Let. Циклические предложения * (s-d)
(type number)8
(type number)1
9
7
nil

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

Let. Циклические предложения

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



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

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

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

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

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

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

Пример

Let. Циклические предложения * (progn (setq x 4) (princ " x = ")
(prin1 x) (princ " m "))
x = 4 m

" m ": значение последнего аргумента.

PRINC обеспечивает гибкий вывод.

TERPRI производит перевод строки. Как значение возвращает nil.
Let. Циклические предложения * (progn (setq x 4) (princ "xxx ") (terpri) (princ "xox "))
xxx
xox
" xox"

Let. Циклические предложения

5.4 Циклические предложения

Циклические вычисления в лиспе выполняются или с помощью итерационных (циклических) предложений

или рекурсивно.

Познакомимся вначале с циклическими предложениями

Let. Циклические предложения

5.4.1 LOOP

Предложение LOOP реализует бесконечный цикл

(LOOP форма1 форма2 .....)

в которoм формы вычисляются до тех пор, пока

не встретится явный оператор завершения RETURN.



Let. Циклические предложения

5.4.1. 1 Применение LOOP для численных итераций.

Определим функцию add-integer, которая будет брать один аргумент, являющийся положительным целым, и возвращает сумму всех чисел между 1 и этим числом.

Let. Циклические предложения 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 раз.
Let. Циклические предложения Например,

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

Let. Циклические предложения * (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 )))))

Пример,

Let. Циклические предложения ( progn (setq x 0)
(loop (if ( = 3 x) (return 't) (print x))
(setq x (+ 1 x))))

0
1
2
t
Определим функцию использующую печать и ввод. Функция без аргументов читает серию чисел и возвращает сумму этих чисел, когда пользователь вводит не число. Функция должна печатать

" Enter the next number: " перед каждым вводом.
Let. Циклические предложения * ( 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>

Let. Циклические предложения

5.4.1. 2 Применение LOOP для итераций co списками.

Предположим, что нам необходима функция double-list, принимающая список чисел и возвращает новый список в котором каждое число удвоено.
Let. Циклические предложения * (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)
Let. Циклические предложения

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 и все повторяется.

Пример

Let. Циклические предложения * ( do (( x 1 ( + 1 x)))
(( > x 10) ( print 'end))
( print x))

Будет печатать последовательность чисел.
В конце end.

Можно сравнить итерационное вычисление с LOOP и DO.

Напишем функцию list-abs, которая берет список чисел и возвращает список абсолютных величин этих чисел.
Let. Циклические предложения (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

Let. Циклические предложения (defun list-abs (lis)
(do ((oldlist lis (cdr oldlist))
(newlist nil (cons (abs (car oldlist)) newlist)))
((null oldlist) (reverse newlist)))))



Может одновременно изменяться значения нескольких переменных
Let. Циклические предложения * ( 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))

Можно реализовать вложенные циклы
Let. Циклические предложения * ( do (( x 1 (+ 1 x)))
(( > x 10))
( do (( y 1 (+ 2 y)))
(( > y 4))
( princ " x= ") ( prin1 x)
( princ " y= ") ( prin1 y)
(terpri) ))

Let. Циклические предложения

5.5.1 Обработка списков c DO.

Напишем функцию, которая будет читать элементы с клавиатуры и объединять в список. Ввод будет закончен, когда будет введен последний элемент end
Let. Циклические предложения ( defun appen-read ()
( do (( x ( list ( read)) ( append x (list (read)))))
(( equal (last x) '(end))); ???? '(end)
(print x)))
( appen-read)

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

Let. Циклические предложения

5.6 DOTIMES

DOTIMES можно использовать вместо DO, если надо повторить вычисления заданное число раз.

Общая форма

(DOTIMES ( var num форма-return) ( форма-тело))

здесь var - переменная цикла,
num - форма определяющая число циклов,
форма - return - результат, который должен быть возвращен.

Прежде всего вычисляется num-форма, в результате получается целое число-count.

Затем var меняется от 0 до count (исключая count) и соответственно каждый раз вычисляется форма-тело.

Последним вычисляется форма-return.

Если форма-return отсутствует, возвращается nil.

Например,

Let. Циклические предложения * (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)

Let. Циклические предложения


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