Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Logicheskoe_i_funktsionalnoe_programmirovanie (...doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
691.2 Кб
Скачать

4.6 Контрольные вопросы

  1. Что называется s-выражением?

  2. Напишите s-выражение, вычисляющее среднее арифметическое чисел 23, 5, 43 и 17.

  3. Определите значения следующих выражений:

    1. ’( + 2 (* 3 4))

    2. (+ 2 ’(* 3 4))

    3. (+ 2 (’* 3 4))

    4. (+ 2 (* 3 ’4))

    5. (quote ’quote)

    6. (quote 2)

    7. ’(quote NIL)

  4. Назовите базовые функции языка Лисп для работы со списками.

  5. Запишите последовательности вызовов CAR и CDR, выделяющие из приведенных ниже списков символ “цель”. Упростите эти вызовы с помощью функций C..R.

    1. (1 2 цель 3 4)

    2. ((1) (2 цель) (3 (4)))

    3. ((1 (2 (3 4 цель))))

  6. Вычислите значения следующих выражений:

    1. (cons nil ’(1 2 3))

    2. (cons nil nil)

    3. (cons ’(nil) ’(nil))

    4. (cons (car ’(a b)) (cdr ’(a b)))

    5. (car ’(car ( a b c)))

    6. (cdr (car (cdr ’(a b c))))

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

  1. ; значение всего выражения

Простейшей функцией ввода в Лиспе является функция 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 вычисляется следующим образом:

  1. Вычисляются предложения условиеi до тех пор, пока не встретится выражение, значением которого не является NIL.

  2. Вычисляются результирующие выражения (выражение), соответствующее этому предикату, и полученное значение возвращается в качестве значения всего предложения cond.

  3. Если истинного предиката условие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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]