Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

MULISP4

.doc
Скачиваний:
14
Добавлен:
09.02.2015
Размер:
75.78 Кб
Скачать

File: MULISP5.LES (c) 27/09/92 СПбЭТУ

CLRSCRN

В процессе этого урока Вы ознакомитесь со следующими темами:

1. Селекторные функции, предназначенные для выбора компонентов

структурированного объекта, представляющего собой двоичное дерево.

2. Конструкторы, используемые для создания двоичного дерева из

простых объектов.

3. Сопоставление итеративного и рекурсивного стилей программирования

на языке muLISP.

CONTINUE

В ходе предыдущих уроков мы с Вами уже рассматривали функции CAR и

CDR и применяли их для обработки списков. Вспомним, как работают эти

функции. Голова (CAR) списка - это его первый элемент. Хвост (CDR) списка

- это сам список, из которого исключен первый элемент. Например:

$ (CAR '(MARS VENUS MERCURY))

$ (CDR (CAR (CDR '((VOLUME 90) (DIMENSIONS 3 5 6) (WEIGHT 2000)))))

Поскольку списки можно интерпретировать также как двоичные деревья,

то при обработке деревьев можно использовать функции CAR и CDR.

CONTINUE

Напомним, что левую ветвь двоичного дерева мы называли также

CAR-ветвью, а правую ветвь двоичного дерева - CDR-ветвью. Поэтому следует

ожидать, что с помощью функции CAR мы сможем выделить левую, а с помощью

функции CDR - правую ветвь двоичного дерева. Приведем примеры такого

использования функций CAR и CDR:

$ (CAR '(GREEN . BLUE))

$ (CDR '(GREEN . BLUE))

$ (CAR (CDR '(YELLOW . (RED . BROWN))))

Во время следующей паузы выделите возобновляемые источники энергии

из двоичного дерева, ассоциированного с переменной ENERGY:

$ (SETQ ENERGY '((OIL . (COAL . SOLAR)) . (WOOD . NUCLEAR)))

Для возвращения в среду урока обратитесь к функции (RETURN) и нажми-

те на клавишу ввода.

BREAK

CLRSCRN

В дополнение к базовым функциям CAR и CDR, в muLISPе можно использо-

вать и их композицию. Имена этих функций включают средние буквы из наиме-

нований CAR и CDR. Например, функция CADR эквивалентна CAR от CDR списка.

Посмотрите на примеры использования этих функций:

$ (CAR (CDR '(DOG CAT COW)))

$ (CADR '(DOG CAT COW))

Все возможные композиции из двух, трех и четырех функций CAR и/или

CDR можно также использовать в muLISPе. Названия этих функций: CDAR,

CAAR, CDDR, CAAAR, CAADR, CADAR, CDAAR и так далее. Обращение к этим

функциям более эффективно, чем последовательные вызовы функций CAR и CDR,

к тому же сокращенное название легче набрать на клавиатуре .

Во время этой паузы переопределите функции SECOND (второй) и THIRD

(третий), используя композицию функций CAR и CDR и проверьте правильность

их работы на примерах.

BREAK

Вот определения функций SECOND и THIRD с использованием композиций

функций:

$ (DEFUN SECOND (LST)

(CADR LST) )

$ (DEFUN THIRD (LST)

(CADDR LST) )

$ (SECOND '(APPLE ORANGE LEMON PEAR))

$ (THIRD '(APPLE ORANGE LEMON PEAR))

CONTINUE

До сих пор мы постоянно получали доступ к элементам списка, начиная

с его левого конца. Однако иногда бывает необходимым выделить последний

элемент списка.

Во время паузы рекурсивно определите селекторную функцию

LAST-ELEMENT (последний-элемент), которая возвращает последний элемент

списка, и проверьте правильность работы этой функции на примерах.

BREAK

Посмотрите на определение функции LAST-ELEMENT с использованием ре-

курсии.Заметьте, что ситуация, при которой список LST оказывается пустым,

должна обрабатываться специальным образом.

$ (DEFUN LAST-ELEMENT (LST)

((NULL LST) NIL)

((NULL (CDR LST))

(CAR LST) )

(LAST-ELEMENT (CDR LST)) )

$ (LAST-ELEMENT '(THE QUICK BROWN FOX))

CONTINUE

До сих пор мы обсуждали так называемый аппликативный стиль програм-

мирования. Для этого стиля характерно употребление композиций функций,

оценивания выражений и рекурсии.

muLISP позволяет также программировать с использованием итерационно-

го стиля. При использовании итерационного стиля главным становится приме-

нение управляющих конструкций, присваивание значений переменным и после-

довательная обработка. Функция LOOP представляет собой одну из важнейших

управляющих конструкций. У этой функции произвольное число аргументов,

которые последовательно оцениваются как формы тела функции, но при этом:

1. После оценивания последней формы функции LOOP управление снова

передается на оценивание первой формы этой функции.

2. Если одна из форм условного предложения, оцениваемая внутри цик-

ла, принимает значение, отличное от NIL, значение этой формы возвращается

как значение функции LOOP, и если вслед за этой функцией есть еще ка-

кая-нибудь форма, то она и оценивается.

Внутри цикла может быть несколько точек выхода. Если таких точек

вовсе нет, то цикл будет повторяться бесконечно.

CONTINUE

Для того, чтобы проиллюстрировать использование конструкции LOOP,

приведем еще одно определение функции LAST-ELEMENT (последний-элемент),

но теперь уже не будем использовать рекурсию:

$ (DEFUN LAST-ELEMENT (LST)

((NULL LST) NIL)

(LOOP

((NULL (CDR LST))

(CAR LST) )

(SETQ LST (CDR LST)) ) )

$ (LAST-ELEMENT '(THE QUICK BROWN FOX))

Во время следующей паузы определите функцию MBR с помощью функции

LOOP, то есть пользуясь итерационным стилем программирования. При вызове

(MBR atom list) возвращается значение T, если <atom> равен (EQL) како-

му-нибудь элементу списка <list>; в противном случае возвращается значе-

ние NIL.

BREAK

Определение функции MBR с использованием итерации:

$ (DEFUN MBR (ATM LST)

(LOOP

((NULL LST) NIL)

((EQL ATM (CAR LST)))

(SETQ LST (CDR LST)) ) )

$ (MBR TED '(BOB CAROL TED ALICE))

CONTINUE

Как Вы, должно быть, уже заподозрили, в muLISPе есть примитивная

функция с именем LAST (последний), которая осуществляет доступ к "правому

концу" заданного списка. В отличие от функции LAST-ELEMENT (послед-

ний-элемент), LAST (последний) возвращает последнюю точечную пару, а не

последний элемент списка. Например:

$ (LAST '(TOKYO WASHINGTON LONDON PARIS))

Функция LAST-ELEMENT может быть определена через функцию LAST следу-

ющим образом:

$ (DEFUN LAST-ELEMENT (LST)

(CAR (LAST LST)) )

$ (LAST-ELEMENT '(TOKYO WASHINGTON LONDON PARIS))

Во время паузы попробуйте догадаться, что будет значением функции

LAST для приведенного ниже списка; проверьте Вашу догадку вызовом функции

LAST:

(23 54 -23 15 . 27)

BREAK

CLRSCRN

Примитивная функция-селектор NTH (N-ый) полезна для извлечения n-го

элемента списка. Если <n> - неотрицательное целое число, то при вызове

(NTH n list) возвращается <n>-ый элемент списка <list>. В muLISPе элемен-

ты списка нумеруются с шагом единица и начальным значением ноль. Поэтому

для выделения первого элемента (то есть car, головы ) списка нужно в ка-

честве аргумента функции NTH, определяющего порядковый номер, задать зна-

чение 0. Например:

$ (NTH 0 '(BOOK PENCIL PAPER PEN))

$ (NTH 2 '(BOOK PENCIL PAPER PEN))

Во время паузы определите функцию INDEXER, используя функцию NTH,

такую, что если список <list2> представляет собой последовательность

чисел, при вызове (INDEXER list1 list2) возвращается список тех элементов

исходного списка <list1>, индексы которых совпадают с индексами, пере-

численными в списке <list2>. Поэтому при вызове

(INDEXER '(A B C D E F G) '(6 2 4 0))

должен быть получен список (G C E A).

BREAK

Приведем наше определение функции INDEXER и пример ее использования:

$ (DEFUN INDEXER (LST1 LST2)

((NULL LST2) NIL)

(CONS (NTH (CAR LST2) LST1) (INDEXER LST1 (CDR LST2))) )

$ (INDEXER '(A B C D E F G) '(6 2 4 0))

CONTINUE

В то время как функция NTH возвращает элемент списка, примитивная

селекторная функция NTHCDR возвращает "хвост" списка. Если <n> - неотри-

цательное число, то при вызове (NTHCDR n list) будет возвращено значение

<n>-го "хвоста" (cdr) списка <list>. Например:

$ (NTHCDR 0 '(BOOK PENCIL PAPER PEN))

$ (NTHCDR 2 '(BOOK PENCIL PAPER PEN))

$ (NTHCDR 5 '(BOOK PENCIL PAPER PEN))

Заметьте, что обе функции, NTH и NTHCDR, возвращают значение NIL,

если в списке недостаточное количество элементов.

CONTINUE

Теперь обсудим примитивные функции-конструкторы muLISPа.

Мы уже описали функцию-конструктор CONS как функцию построения

списков. CONS может быть также использована для построения двоичных де-

ревьев. Функция CONS создает единственную точечную пару, CAR которой яв-

ляется первым аргументом, а CDR - вторым аргументом вызова. Например:

$ (CONS 'AGE 43)

Если Вы хотите попрактиковаться в создании двоичных деревьев с по-

мощью функции CONS постройте вот такое двоичное дерево:

(((IBM . PC) . (APPLE . MACINTOSH)) . (TANDY . TRS-80))

BREAK

Дерево с информацией о компьютерных фирмах и их продукции может быть

построено так:

$ (CONS (CONS (CONS IBM PC) (CONS APPLE MACINTOSH)) (CONS TANDY TRS-80))

Как мы уже отмечали, двоичные деревья изображаются с помощью смешан-

ной нотации, DOT (точечной) и LIST (в форме списка).

CONTINUE

Предположим, что нам необходимо создать список значений переменных

FIRSTNAME (имя), LASTNAME (фамилия), and ADDRESS (адрес). Например, если

бы эти переменные получили следующие значения:

$ (SETQ FIRSTNAME 'Jane)

$ (SETQ LASTNAME 'Smith)

$ (SETQ ADDRESS '(Honolulu Hawaii))

то можно было бы запомнить такую информацию, используя функцию CONS:

$ (CONS FIRSTNAME (CONS LASTNAME (CONS ADDRESS NIL)))

CONTINUE

Вместо того, чтобы вызывать функцию CONS для присваивания значения

очередной переменной, можно использовать примитивную функцию LIST, с по-

мощью которой можно достичь желаемого результата более удобным способом:

Функция LIST может иметь любое число аргументов. Она возвращает в качест-

ве значения список аргументов. Например:

$ (LIST FIRSTNAME LASTNAME ADDRESS)

CONTINUE

Хотя на одном из предыдущих уроков мы с Вами определили функцию

APPEND, на самом деле она является примитивной функцией muLISPа. "Машин-

ное" определение функции APPEND более гибкое, чем определение пользовате-

ля, так как позволяет использовать любое число списков-аргументов. Напри-

мер:

$ (APPEND '(DOG CAT COW) '(SNAKE LIZARD CROCODILE) '(TROUT SALMON TUNA))

CONTINUE

Различие между тремя функциями-конструкторами может привести к неко-

торым недоразумениям. Посмотрите внимательно на результаты вызовов этих

функций в применении к одним и тем же аргументам:

$ (CONS '(DOG CAT) '(COW PIG))

$ (LIST '(DOG CAT) '(COW PIG))

$ (APPEND '(DOG CAT) '(COW PIG))

CONTINUE

В одном из уроков мы определили функцию REVERSE, используя "накапли-

вающую" переменную. Вот то определение:

$ (DEFUN REVERSE (LST1 LST2)

((NULL LST1) LST2)

(REVERSE (CDR LST1) (CONS (CAR LST1) LST2)) )

$ (REVERSE '(A B C D E))

Во время паузы определите функцию REVERSE в итерационном стиле,

используя управляющую конструкцию LOOP. Вы можете употребить ту же техни-

ку использования "накапливающей" переменной.

BREAK

Вот альтернативное определение функции REVERSE:

$ (DEFUN REVERSE (LST1 LST2)

(LOOP

((NULL LST1) LST2)

(SETQ LST2 (CONS (CAR LST1) LST2))

(SETQ LST1 (CDR LST1)) ) )

$ (REVERSE '(A B C D E))

CONTINUE

Зачастую во время последовательной обработки списка в цикле мы обра-

щаемся много раз к функции CAR, а затем укорачиваем список на один эле-

мент. Эта процедура эквивалентна чтению одного элемента из стека.

Примитивная функция muLISPа с именем POP ("прочитать элемент из сте-

ка") упрощает использование описанной процедуры. Если <stack> представля-

ет собой символ, значение которого - список , обращение (POP stack)

обеспечит получение "головы" (CAR) списка и одновременно сделает значение

<stack> равным "хвосту" (CDR) списка. Например, в определении функции

REVERSE итерационным способом вместо двух форм,

(SETQ LST2 (CONS (CAR LST1) LST2))

(SETQ LST1 (CDR LST1))

можно использовать одну, если применить функцию РОР:

(SETQ LST2 (CONS (POP LST1) LST2))

CONTINUE

Другая операция, которая часто используется при циклической обработ-

ке списка, - это присоединение нового элемента в начало списка с помощью

функции CONS, что можно сделать так:

(SETQ stack (CONS object stack))

С помощью примитивной функции PUSH можно короче выразить эту опера-

цию:

(PUSH object stack)

Во время паузы переопределите функцию REVERSE итерационным способом,

употребив функции PUSH и POP.

BREAK

Вот наше определение функции REVERSE с использованием PUSH и POP:

$ (DEFUN REVERSE (LST1 LST2)

(LOOP

((NULL LST1) LST2)

(PUSH (POP LST1) LST2) ) )

$ (REVERSE '(A B C D E))

После того, как мы написали 4 разных определения функции REVERSE, я

осмелюсь признаться, что на самом деле REVERSE является примитивной функ-

цией muLISPа ! Разумеется, "машинная" версия, как обычно, более эффектив-

на.

CONTINUE

Когда заканчивается вычисление функции и наступает момент возвращать

вычисленное значение, muLISP автоматически восстанавливает состояние сре-

ды, которое имелось в момент вызова функции. Это означает, что Вы можете,

не задумываясь, менять значения формальных переменных, не запоминая те

значения, которые они имели перед началом вычислений функции. По этой

причине функция рассматривается как "черный ящик", и обращение к функции

не влияет на состояние среды (контекст) за исключением того, что в ре-

зультате вычисления функции возвращается значение. В случае необходимости

функция может возвратить не одно значение, а целый список.

Другой способ влияния функции на среду - это присваивание в теле

функции значений переменным, которые не включены в список формальных па-

раметров. Такие переменные называются специальными ("special", "fluid")

или глобальными ("global"). Использование таких переменных сопряжено с

опасностью не заметить возможности их изменения при внесении изменений в

программу и тем самым внести ошибки.

CONTINUE

При реализации функции REVERSE с использованием рекурсии или итера-

ции обращение к функции занимает время пропорциональное длине аргумента.

Однако для длинных списков итеративная версия работает примерно на 20 %

быстрее, в зависимости от типа компьютера и объема доступной оперативной

памяти. С другой стороны, запись рекурсивной функции более компактна.

Если Вам нужно осуществлять выбор, предпочитая либо скорость работы, либо

компактность записи, можно посоветовать стремиться предпочесть быстрые

варианты реализации наиболее часто используемых функций, а в остальных

случаях отдать предпочтение компактности записи.

Другое обстоятельство, которое нужно принимать во внимание при выбо-

ре рекурсивного или итеративного способа программирования, заключается в

учете требуемого объема памяти для решения поставленной задачи. Каждый

раз при вызове функции запоминаются адрес точки возврата и текущие значе-

ния формальных аргументов, для чего организуется стек (STACK), и при за-

вершении вычисления функции восстанавливается предыдущее состояние среды.

Поскольку при реализации рекурсивного процесса производится запоминание

прерванных процессов в стеке, то может быть израсходована вся доступная

память прежде, чем закончится вычисление функции. Это может вызвать ошиб-

ку "Memory Space Exhausted" (Объем памяти израсходован). При использова-

нии итеративного варианта вычисления функции объем доступной памяти может

оказаться вполне достаточным для завершения вычислений.

CONTINUE

В muLISPе имеется три примитивных логических функции. Функция OR

имеет произвольное число аргументов и производит оценивание их в порядке

слева направо до тех пор, пока не встретится аргумент, у которого значе-

ние отлично от NIL либо список аргументов исчерпан. Если обнаружен аргу-

мент, значение которого отлично от NIL, функция OR возвращает значение

этого аргумента; в противном случае возвращается значение NIL.

Вы можете использовать то обстоятельство, что после того, как в

списке аргументов встретился аргумент со значением отличным от NIL, все

остальные аргументы будут проигнорированы. Если аргумент имеет значение

отличное от NIL (ложь), то это не обязательно должно быть T (истина), по-

этому возвращаемое функцией значение не обязательно должно быть равно T

или NIL. С точки зрения управления работой программы muLISP рассматривает

любое значение отличное от NIL как T, что может оказаться очень удобным

для программиста.

Напомним, что атом в muLISPе является символом или числом. Поэтому

функция-распознаватель может быть определена следующим образом:

$ (DEFUN ATOM (U)

(OR (SYMBOLP U) (NUMBERP U)) )

CONTINUE

Аналогично функции OR (логическое ИЛИ), имеется встроенная функция

AND (логическое И), которая также имеет произвольное число аргументов.

AND последовательно оценивает аргументы до тех пор, пока не встретится

значение NIL либо список аргументов не окажется исчерпанным. AND возвра-

щает значение последнего из оцениваемых аргуметов. Поэтому Вы можете

использовать то обстоятельство, что после того, как в списке аргументов

встретился аргумент со значением NIL, все остальные аргументы будут про-

игнорированы. Например:

$ (AND (SYMBOLP 'frog) (NUMBERP 7))

CONTINUE

Примитивная функция NOT возвращает значение T, если ее аргумент име-

ет значение NIL; в противном случае она возвращает значение NIL. Напри-

мер:

$ (NOT (ATOM '(SODIUM . CHLORIDE)))

Как мы уже упоминали ранее, функция NOT не идентична функции NULL.

NULL используется для выяснения, является ли ее аргумент пустым списком.

Функция NOT используется для тестирования логического значения.

CONTINUE

Подитожим то, что мы обсуждали в течение этого урока:

1. Мы выяснили, как можно использовать 28 функций-примитивов,

представляющих собой композицию функций CAR и CDR (CADR, CDDR, CAAAR, и

так далее), для доступа к компонентам двоичного дерева.

2. Обсудили функции NTH и NTHCDR для работы со списком.

3. Установили различие между тремя функциями-конструкуторами CONS,

LIST и APPEND.

4. Попробовали программировать с применением итерационного стиля с

помощью управляющей конструкции LOOP, а также функций для работы со сте-

ком: PUSH и POP .

5. Рассмотрели логические функции AND, OR и NOT.

А теперь попробуйте самостоятельно запрограммировать описанные

ниже функции с применением итеративного стиля программирования:

1. (UNION set1 set2) возвращает в качестве значения результат теоре-

тико-множественное объединения множеств <set1> и <set2>.

(Множество задается списком, в котором не могут встречаться одинако-

вые элементы.)

2. (INTERSECTION set1 set2) возвращает пересечение двух множеств.

3. (SUBST new old list) заменяет все вхождения элемента <old> в

список <list> на элемент <new>.

CONTINUE

$ (RDS)

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