
- •ОгЛавление
- •8 Литература 93
- •Лабораторная работа №1 Знакомство с языком логического программирования Пролог
- •1.1 Цель работы
- •1.2 Теоретические сведения
- •1.3 Среда VisualProlog
- •1.4 Выполнение работы
- •1.5 Задание на лабораторную работу
- •2.3 Выполнение работы
- •2.4 Задание на лабораторную работу
- •2.5 Контрольные вопросы
- •Лабораторная работа №3 Рекурсивные структуры данных
- •3.1 Цель работы
- •3.2 Теоретические сведения
- •3.3 Выполнение работы
- •3.4 Задание на лабораторную работу
- •3.5 Контрольные вопросы
- •Лабораторная работа №4 Знакомство с языком списочных структур Лисп
- •4.1 Цель работы
- •4.2 Теоретические сведения
- •4.3 Среда FreeLisp
- •4.4 Выполнение работы
- •4.5 Задание на лабораторную работу
- •4.6 Контрольные вопросы
- •Лабораторная работа №5 Рекурсия и итерация в языке Лисп
- •5.1 Цель работы
- •5.2 Теоретические сведения
- •5.3 Выполнение работы
- •5.4 Задание на лабораторную работу
- •5. 5. Контрольные вопросы
- •6.2.2 Замыкания
- •6.2.3 Макросы
- •6.3 Задание на лабораторную работу
- •6.4 Контрольные вопросы
- •Лабораторная работа №7 Методы поиска решений в пространствах состояний
- •7.1 Цель работы
- •7.2 Теоретические сведения
- •7.4 Выполнение работы
- •7.5 Задание на лабораторную работу
- •7.6 Контрольные вопросы
- •8 Литература
4.6 Контрольные вопросы
Что называется s-выражением?
Напишите s-выражение, вычисляющее среднее арифметическое чисел 23, 5, 43 и 17.
Определите значения следующих выражений:
’( + 2 (* 3 4))
(+ 2 ’(* 3 4))
(+ 2 (’* 3 4))
(+ 2 (* 3 ’4))
(quote ’quote)
(quote 2)
’(quote NIL)
Назовите базовые функции языка Лисп для работы со списками.
Запишите последовательности вызовов CAR и CDR, выделяющие из приведенных ниже списков символ “цель”. Упростите эти вызовы с помощью функций C..R.
(1 2 цель 3 4)
((1) (2 цель) (3 (4)))
((1 (2 (3 4 цель))))
Вычислите значения следующих выражений:
(cons nil ’(1 2 3))
(cons nil nil)
(cons ’(nil) ’(nil))
(cons (car ’(a b)) (cdr ’(a b)))
(car ’(car ( a b c)))
(cdr (car (cdr ’(a b c))))
(list (list ’a ’b) ’(car (c d)))
Лабораторная работа №5 Рекурсия и итерация в языке Лисп
5.1 Цель работы
Изучить возможности использования рекурсивных и итерационных алгоритмов в языке Лисп.
5.2 Теоретические сведения
Рекурсия и итерация
Для решения многих видов вычислительных задач требуется неоднократно повторять одни и те же алгоритмические процессы. Такие процессы могут быть описаны с помощью рекурсивных или итеративных процедур. Рекурсивная процедура или функция – это подпрограмма, которая вызывает сама себя, т.е. выполнению рекурсивной подпрограммы предшествует выполнение ее собственной копии. При выполнении рекурсивной подпрограммы осуществляется многократный переход от некоторого текущего уровня организации алгоритма к нижнему уровню последовательно до тех пор, пока, не будет получено тривиальное решение поставленной задачи. Итерация обычно бывает более эффективной, чем рекурсия, поскольку для завершения шага итерации – в отличие от шага рекурсии – не требуется ожидать результатов выполнения последующих шагов. Использование итерации, следовательно, позволяет избежать расходов, связанных с организацией в период исполнения стека латентных вызовов (вызовов еще ожидающих активации), что невозможно при использовании рекурсии. Рекурсивная форма организации алгоритма обычно выглядит изящнее итерационной и дает более компактный текст программы, но при выполнении, как правило, медленнее и может вызвать переполнение стека латентных вызовов.
Рекурсия также используется для описания структур данных частично состоящих или определяемых с помощью самих себя. Примерами таких рекурсивных структур данных являются списки и деревья. Например, список можно определить следующим образом:
<список>:=<пустой>|<элемент>|<список><элемент>
Достоинство рекурсивного определения объекта состоит в том, что оно позволяет с помощью конечного высказывания определить бесконечное множество объектов.
Для рассмотрения примеров написания программ на языке Лисп, содержащих рекурсивные или итерационные функции, нам необходимо познакомиться с некоторыми функциями языка Лисп, которые могут понадобиться для написания этих программ.
Определение функций
Определение функций и их вычисление в Лиспе основано на лямбда-исчислении Черча. В лямда-исчислении функция записывается в следующем виде:
lambda (x1, x2, .., xn).fn
В Лиспе лямда-выражение имеет вид:
(LAMBDA (x1 x2 ... xn) fn)
где xi – формальные параметры,
fn – тело функции
Лямда-выражение – это безымянная функция, которая пропадает тотчас после вычисления значения формы. Дать имя и определить новую функцию можно с помощью функции defun:
(defun имя_функции (список_аргументов) тело_функции)
Значением этой формы является имя новой функции.
Например, определим функцию, реализующую сложение двух чисел:
>(defun add (a b) (+ a b))
ADD
Заметьте, что интерпретатор вернул имя определенной пользователем функции в качестве значения s-выражения. Теперь мы можем использовать функцию add подобно любой встроенной лисповской функции.
>(add 4 5)
9
Функции ввода/вывода
Рассмотрим функции, позволяющие поддерживать обмен данными между человеком и компьютером, то есть функции ввода/вывода.
Наиболее простой функцией вывода в Лиспе является функция print с одним аргументом. Эта функция вначале вычисляет значение переданного в качестве аргумента выражения, а затем выводит на экран его значение.
Например:
>(print “Hi”)
“Hi” ; вывод (эффект)
“Hi” ; значение
Причина по которой строка “Hi” выводится на экран дважды заключается в том, что функция print как и другие лисповские функции возвращает значение. Таким образом, первая строка “Hi” является результатом работы функции print (печать выражения), а вторая строка “Hi” является значением, возвращаемым формой print. Это позволяет использовать функцию print, как и другие функции ввода/вывода, в качестве аргументов других функций.
Например:
>(+ (print 2) 3)
2 ; результат работы функции print
; значение всего выражения
Простейшей функцией ввода в Лиспе является функция read. Эта функция читает s-выражение и возвращает его. Например:
>(read)
“Hi” ; введенное пользователем выражение
“Hi” ; возвращенное значение
>(setq input (read))
(+ 2 3) ; введенное выражение
(+2 3) ; значение
>input
(+ 2 3)
Последовательные вычисления
Хотя функциональный стиль является стилем программирования на Лиспе, Лисп содержит необходимые средства и для обыкновенного императивного или операторного программирования. Операторное программирование обеспечивается в Лиспе операторами присваивания, условными операторами и операторами передачи управления, а также рядом предложений, поддерживающих циклы. Составные операторы и фрагменты в Лиспе можно строить, например, формами PROG. Предложения PROG позволяют работать с несколькими вычисляемыми формами:
(PROG1 форма1 форма2 ... формаN)
(PROG2 форма1 форма2 ... формаN)
(PROGN форма1 форма2 ... формаN)
У этих специальных форм переменное число аргументов, которые они последовательно вычисляют, а в качестве значения всей формы возвращают значение первого (PROG1), второго (PROG2) или последнего (PROGN) аргумента.
Например:
>(prog1 1 2 3 4)
1
>(prog2 1 2 3 4)
2
>(progn 1 2 3 4)
4
Условные предложения
Лисп поддерживает несколько форм условных предложений. Наиболее простым является функция if, которая напоминает функцию if в языках C, Pascal, и т. д. Рассмотрим пример использования функции if:
> (if (= 3 (+ 2 1)) 0 1)
0
Структура формы if следующая:
(if условие форма1 форма2)
Вначале вычисляется значение выражения условие. Если его значение не равно NIL, выполняется форма форма1 и ее значение возвращается в качестве значения формы if. Если же значение выражения условие равно NIL, то выполняется форма форма2 и ее значения возвращается в качестве значения формы if.
Лисп имеет и более мощную, хотя и более сложную, функцию проверки условия cond. Структура условного предложения cond следующая:
(cond (условие1 выражение ... выражение)
(условие2 выражение ... выражение)
...
(условиеN выражение ... выражение))
Значение предложения COND вычисляется следующим образом:
Вычисляются предложения условиеi до тех пор, пока не встретится выражение, значением которого не является NIL.
Вычисляются результирующие выражения (выражение), соответствующее этому предикату, и полученное значение возвращается в качестве значения всего предложения cond.
Если истинного предиката условиеi нет, то значением cond будет NIL.
Очень часто предложение cond используется аналогично оператору switch в языке C. Чтобы реализовать часть default в предложении cond просто используйте атом T в качестве последнего предиката.
Например:
>(cond
((= 1 2) (print “1=2”))
(t (print “1<>2”))
)
“1<>2”
“1<>2”
Другими вариантами условных предложений являются формы WHEN и UNLESS. Структура предложения when следующая:
(when условие форма1 форма2 ...)
Использование предложения when рассмотрим на следующих примерах:
>(when (= 3 (+ 2 1)) 1)
1
>(when (= 3 (+ 2 2)) 1)
NIL
Структура предложения UNLESS следующая:
(unless (NOT условие) форма1 форма2 ...)
Например:
>(unless (= 3 2) (+ 2 5))
7
>(unless (= 3 3) (+ 2 5))
NIL
Также можно применять подобное используемому в языке Паскаль выбирающее предложение CASE:
(case ключ
(список-ключей1 форма11 форма12 ...)
(список-ключей2 форма21 форма22 ...)
...)
При выполнении case сначала вычисляется значение формы ключ. Затем его сравнивают с элементами списков-ключей. Когда в списке найдено значение ключевой формы, начинают выполняться следующие за списком ключей формы. Значение последней вычисленной формы и возвращается в качестве значения всего предложения case.
Например:
>(setq k 4)
4
>(case k
((1 2 3) 1)
((4 5 6) 2))
2
Циклические вычисления
Наиболее простой итеративной конструкцией в Лиспе является функция loop, которая получает в качестве аргументов серию выражений m1, m2, …, mn:
(loop m1 m2 ...)
Функция loop реализует цикл, в котором формы m1, m2, … вычисляются снова и снова. Завершается цикл, если в какой-либо из форм встретится оператор завершения return или другая форма прекращающая вычисления.
Например:
>(loop (print “Hi”))
“Hi”
“Hi”
“Hi”
...
Лисповская функция dotimes подобна циклу FOR-NEXT в языке BASIC: определенная задача выполняется фиксированное количество раз. Например, следующая функция посчитает от 0 до 9:
(dotimes (x 10) (print x))
0
1
...
9
NIL
Структура функции dotimes следующая:
(dotimes (переменная счетчик результат) выр1...вырn)
Внутренняя переменная (переменная) на каждой итерации цикла изменяет свое значение от 0 до счетчик-1. Выражения выр1 ... вырn вычисляются на каждом шаге цикла. По окончании вычисляется выражение результат и возвращается его значение. Если выражение результат не задано, то возвращается NIL.
Функция dolist имеет подобную структуру. Однако в этом случае итерация выполняется над всеми элементами списка. Например:
>(dolist (x ’(a b c)) (print x))
A
B
C
NIL
Самым общим циклическим или итерационным предложением в Лиспе является do. Предложение do имеет следующую структуру:
(do ((перем1 знач1 шаг1)
(перем2 знач2 шаг2)
...
(перемk значk шагk))
(условие результат1 результат2 ... результатm)
выр1 ... вырn)
где:
перем1, перем2, ..., перемk – внутренние переменные
знач1, знач2, ..., значk – их начальные значения
шаг1, шаг2, ..., шагk – формы обновления
Вычисление предложения do начинается с присваивания значений переменным перем1, перем2, ..., перемk. Переменным, начальное значение (значi) которых не задано, по умолчанию присваивается NIL. В каждом цикле после присваивания значения переменной вычисляется условие окончания – условие. Как только значение условия не равно NIL, т.е. условие окончания истинно, последовательно вычисляются формы результат1, результат2, ..., результатm, и значение последней формы возвращается как значение всего предложения do. В противном случае последовательно вычисляются формы выр1 ... вырn из тела предложения do. На следующей итерации цикла переменным перемi присваиваются (одновременно) значения форм шагi, вычисляемых в текущем контексте, проверяется условие окончания и т.д. Если для переменной не задана форма обновления, то значение переменной не меняется. Например:
> (do ((x 0 (+ x 1)))
((> x 9))
(print x))
0
1
...
9
NIL