Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Языки логического программирования. Учебное пособие
.pdf
В результате первого вызова функции rdr в поле зрения появится
ссылка на динамический ящик (как содержимое ящика A ), а в результате
второго вызова этой функции в поле зрения будет возвращено
содержимое динамического ящика. Это и используется в
вышеприведенной программе. Прокомментируем некоторые сложные
функции этой программы.
Добавление элемента определяется функцией In. Ее выполнение
начинается с получения ссылки в ящике головы списка pl (для первого
вводимого элемента списка эта ссылка пустая и означает конец списка, а
для последующих данных, определяемых динамическими ящиками, она
уже не пустая), которая является первой частью тройки добавляемого
элемента. Затем осуществляется ввод ключа нового элемента и
определение этого значения в структурных скобках и, наконец, ввод
данных этого элемента и определение этого значения в качестве
последней части тройки элемента. После этого эти данные
записываются в новый динамически определяемый элемент, а ссылка
на него записывается в ящик головы списка.
Поиск элемента по ключу определяется функцией Find и
вспомогательными функциями f1, f2, f3, f4. Действия функции
Find начинаются с записи в ящик pred предыдущего элемента пустой
ссылки и с записи в ящик cur текущего элемента ссылки на первый
элемент списка, находящейся в ящике pl головы списка. Затем для
текущего элемента списка вызывается функция f1, которая в случае
неуспешности поиска (ссылка на текущий элемент пустая) записывает в
ящик data отыскиваемых данных фразу 'Не найден'. В случае же
непустой ссылки на текущий элемент f1 вызывает функцию f2,
передавая ей на анализ этот элемент. Функция f2 анализирует данные
текущего элемента, разбивая его на ссылку на следующий элемент
(записывается в ящик next ), ключ и данные (записываются в ящик
data данных). Затем она отправляет этот ключ функции f3 (для
сравнения с поисковым ключом в ящике key ), которая готовит
разность ключей и отправляет ее для решения функции f4. Эта функция
при равенстве ключей (разность ключей равна 0) заканчивает
вычисления поиска (в ящике data уже находятся искомые данные), а в
ином случае сначала изменяет ссылку текущего элемента на следующий
элемент, а ссылку предыдущего элемента - на прежний текущий
Языки логического программированияВ.С. Рублев
51

элемент, и затем вызывает функцию f1 для нового текущего элемента.
Удаление элемента по ключу осуществляется функцией Out и
вспомогательными функциями o1, o2, o3. Действия начинаются с
поиска Find элемента по ключу и передачи текущего определенного
элемента функции o1 для анализа. Функция o1 при пустой ссылке
текущего элемента записывает в ящик data неуспешность поиска, а в
ином случае передает эту ссылку функции o2 для подготовки удаления
элемента из списка. Функция o2 в случае пустой ссылки предыдущего
элемента изменяет ссылку в головном указателе pl на ссылку на
следующий элемент (и этим ликвидирует текущий элемент списка), а в
случае непустой ссылки предыдущего элемента передает функции f3
эту ссылку для удаления текущего элемента списка. Функция f3
записывает в ящик pred предыдущего элемента ссылку на следующий
элемент, сохраняя остальные данные предыдущего элемента. Этим
также заканчивается удаление текущего элемента.
Упражнения
1. Разработайте рекурсивную программу на Рефале-2 вычисления
числа Фибоначчи с вводимым номером.
2. Разработайте рекурсивную программу на Рефале-2 сортировки
последовательности целых чисел, введенных в один из стеков
копилки, при помощи двух других стеков копилки.
3. Разработайте программу-интерпретатор на Рефале-2 вычисления
арифметического выражения с целыми неотрицательными
числами, операциями сложения и умножения и скобками,
определяющими порядок выполнения операций.
4. Разработайте программу на Рефале-2 для динамического графа,
которая бы добавляла и удаляла вершины и ребра, а также
выводила граф на устройство вывода.
1)
от bury - закопать.
2)
от dig out - выкопать.
3)
от copy - скопировать.
4)
от replace - заменить.
5)
от dig out all - выкопать все.
Языки логического программированияВ.С. Рублев
52

Язык РЕФАЛ: Рефал-5
Синтаксические отличия Рефала-5 от Рефала-2; пример программы n!
на Рефале-5. Условие в рефал-предложении Рефала-5: пример проблемы
недостаточной наглядности при организации взаимной рекурсии двух
функций; синтаксис условия и его семантика; решение проблемы
недостаточной наглядности введением условия в рефал-предложение;
примеры использования условия в функции сложения Add и решении
задачи о назначениях. Блок как структурирующая конструкция Рефала-5:
улучшение наглядности программы; пример сортировки слиянием двух
упорядоченных последовательностей; образцовый окончание и
образцовый блок рефал-предложения; результатный блок и результатное
окончание рефал-предложения; пример функции сравнения множеств.
Рефал-5
Синтаксические отличия
Рефал-5 является современной версией Рефала, введенной автором в
конце прошлого века. Некоторые отличия являются только
синтаксическими и связаны с тем, что язык стал использоваться с
современными операционными системами (в частности, с Windows).
Перечислим их сначала, а затем перейдем к описанию новых
структурирующих конструкций, делающих язык еще более лаконичным
и мощным.
1. Литеры, как и прежде, записываются в апострофах, но символыметки записываются без ограничителей (косой черты) или в
двойных кавычках.
2. Числа (ранее символы-числа) представляют собой
последовательность цифр со знаком "+" или "-" либо без знака.
3. Переменные начинаются со строчной буквы s, t или e, за
которой следует индекс - цепочка букв, цифр и знаков "-", "_". s переменные имеют значением символы, t -переменные - термы,
а e -переменные - любые выражения, включая пустые. Точка
может отделять первую букву переменной от ее индекса.
4. Левая часть рефал-предложения называется образцом , а правая
Языки логического программированияВ.С. Рублев
53

часть - результатным выражением . Каждое рефал-предложение
заканчивается точкой с запятой.
5. Определение функции имеет следующий синтаксис:
имя-функции { последовательность рефал-предложений };
1)
6. Вызов функции записывается в уголковых скобках "<" и ">", т.е.
функциональными скобками являются уголковые.
7. Комментарии записываются одним из следующих способов:
строка, начинающаяся со знака "*";
окончание строки после "//";
между "/*" и "*/".
8. Входом в программу является всегда функция GO, с которой
начинается выполнение.
9. Директив нет. Внешние функции перечисляются после ключевого
слова $EXTERNAL (или $EXTERN, или $EXTRN ). Функция,
являющаяся входом (используется в других модулях), описывается
после ключевого слова $ENTRY.
В качестве примера оформления модулей в Рефале-5 рассмотрим
приведенное ниже описание функции факториал Fact в модуле M1 и
ее вызов в модуле M2:
Файл M1.REF:
* Функция Fact вычисляет произведение чисел от 1 до sN
* Здесь используются краткие обозначения для
* встроенных арифметических функций
$ENTRY Fact {
0 = 1;
sN = <* sN <Fact <– sN 1>>>;
};
Файл M2.REF:
* Головной модуль для вызова функции Fact
* Здесь используются расширения Рефала,
* а также встроенные функции ввода-вывода
* и преобразований символов
$EXTERN Fact;
$ENTRY GO { ,
Языки логического программированияВ.С. Рублев
54

<PRINTLN 'Введи число:'> ,
<READ_LINE> : {
= ;
e1 = <PRINTLN 'Fact(' e1 ') = ' <Fact <NUMB e1>>> <GO>;
};
};
В модуле М1 используются стандартные арифметические функции "*"
для умножения и "-" для вычитания (перечисление стандартных
функций в Рефале-5 не требуется).
В модуле M2 ввод числа функцией READ\_LINE и вычисление
факториала от этого числа повторяется (вызов функции GO ).
Окончанием работы программы является ввод пустой строки.
Введенное число преобразуется из символьного представления в
числовое стандартной функцией Numb, затем вызывается внешняя
функция Fact вычисления факториала от введенного числа, после чего
следует вывод строки:
Fact ( значение_числа ) = значение_факториала:
Условие
Введение условия в рефал-предложение позволяет увеличить мощь
Рефала за счет значительного сокращения написания дополнительных
функций.
Для того чтобы продемонстрировать смысл и значение вводимой
конструкции, мы сначала рассмотрим пример:
требуется написать функцию CBD, которая из входной строки выделяет
ее начало, ограниченное одним из некоторого множества символов: {"|", ",", ";"}.
Для решения этой задачи требуется во входной строке искать слева
направо первый из указанного множества символов. Можно было бы
для каждого символа из указанного множества написать свое рефалпредложение, но неясно, какой из них является в строке первым. К тому
же в примере множество содержит только 3 символа, но если
Языки логического программированияВ.С. Рублев
55

значительно расширить множество, то придется в решение вставлять
очень много рефал-предложений, т. е. получить плохое
неструктурированное решение. Поэтому будем решать задачу поэтапно.
Прежде всего заметим, что задача нахождения в строке терма из
некоторого множества является достаточно общей и полезно написать
функцию Include ("содержит"), которая узнает, содержит ли данный
список термов заданный терм:
Include {
(e1 tA e2) tA = T;
(e1) tA = F;
};
Обращение к ней имеет вид: <Include (eL) tA>. Результатом
является символ-слово T, если список eL содержит терм tA, а в
противном случае - символ-слово F. Структурные скобки позволяют
улучшить понятность выражения-аргумента этой функции.
Теперь вернемся к основной функции CBD. Можно по очереди
рассматривать символы строки и проверять, содержится ли очередной
символ в указанном множестве, для чего вызывать функцию Include.
Анализ результата вызова Include можно поручить какой-либо
вспомогательной функции, скажем CBDO, которая в случае
отрицательного результата (значение F ) снова вызовет функцию CBD
для анализа уже следующего символа, а в случае положительного
результата (значение T ) удалит остаток строки и закончит работу.
Решение это будет выглядеть следующим образом:
CBD {
sA eL = <CBDO sA eL <Include ('|,;') sA> >;
= ;
};
CBDO {
sA e1 F = sA <CBD e1>;
sA e1 T =;
};
Такое решение, связанное с взаимной рекурсией двух функций,
Языки логического программированияВ.С. Рублев
56

недостаточно наглядно. Хотелось бы в рефал-предложение функции
CBD ввести условие так, чтобы в случае его выполнения (символ не
принадлежит указанному множеству), мы могли бы продолжить
проверку следующего такого символа за счет рекурсивного обращения.
В случае же невыполнения условия (очередной символ принадлежит
указанному множеству) мы должны закончить анализ и отбросить
дальнейшую часть строки. Это решение имело бы следующую структуру:
CBD {
sA eL, [sA не принадлежит множеству] = sA <CBD eL>;
eL = ;
};
Определим теперь синтаксис условия. Условие записывается после
образца в виде:
, Er : Ep,
где Er - произвольное результатное выражение, а Ep - произвольный
образец. В одном рефал-предложении может быть несколько условий.
Выражение Er может содержать переменные, которые уже встречались
в этом же предложении слева от запятой. Образец Ep может содержать
как старые, так и новые переменные. Условия "работают"
последовательно, после того как успешно завершится основное
сопоставление, следующим образом:
1. Значения старых переменных подставляются в Er и Ep, и в
результате получаются рабочее выражение E'r и новый образец
E'p.
2. Затем E'r вычисляется, и результат вычислений - объектное
выражение E'r - сопоставляется с образцом E'p.
3. В случае успеха новые переменные с их значениями добавляются
к списку старых переменных, и продолжается вычисление
предложения (следующих условий или правой части).
4. В случае неуспеха работа переходит на предыдущий образец:
последний вариант сопоставления отвергается, и продолжается
поиск других вариантов.
Языки логического программированияВ.С. Рублев
57

Перепишем функцию CBD с использованием условия:
CBD {
sA eL, <Include ('|,;') sA> : F = sA <CBD eL>;
eL = ;
};
Заметим, что второе предложение объединяет два случая: когда строка
начинается с разделителя и когда она пуста.
Мы можем еще упростить эту функцию, используя правило
отождествления слева направо, при котором будет автоматически
искаться первый символ, принадлежащий указанному множеству:
CBD {
e1 sA e2, <Include ('|,;') sA> : T = e1;
e1 = e1;
};
Последний вариант иллюстрирует возможность того, что неуспех в
условии не отвергает сразу предложение, а вызывает переход к
следующему варианту сопоставления в предыдущем образце.
Название введенной конструкции " условие " не совсем точно отражает
ее смысл: кроме проверки условия, ее результатом является также
присваивание значений новым переменным. Часто именно в них и
заключен весь смысл условия. Будем содержательно различать 3 вида
условий:
1. чистые условия - не определяют новых переменных (как в
примере);
2. чистые присваивания - безусловно срабатывают и для этого
используются;
3. условные присваивания - смешанный случай.
Рассмотрим другой пример сложения: функция Add складывает два
числа, представленные в виде цепочек десятичных цифр, используя
функцию сложения двух цифр Ad-digits:
Add {
Языки логического программированияВ.С. Рублев
58

(e1 sA) (e2 sB) , <Ad-digits sA sB> : e.Car s.Sum
= <Add (<Add (e1) (e2)>) (e.Car)> s.Sum;
(e1) (e2) = e1 e2;
};
Ad-digits {
'0' sX = sX;
sX '0' = sX;
'11' = '2';
. . .
'99' = '18';
};
Условие в первом предложении содержательно является чистым
присваиванием, поскольку, с учетом возможных результатов функции
Add-digits, сопоставление всегда выполняется успешно. В
результате выполнения условия происходит сложение младших
разрядов обоих чисел и результат сложения присваивается: младший
разряд - переменной s.Sum, а старший разряд - разряд переноса переменной e.Car (может иметь пустое значение, которое трактуется
как '0'). За счет рекурсивного обращения к функции Add процесс
сложения разрядов идет справа налево с добавлением переноса. Как
только одно из чисел станет пустым (все его разряды будут сложены с
разрядами другого), слева к результату сложения младших разрядов
дописывается левая часть, оставшаяся от другого числа. Таким образом,
программа демонстрирует экономное сложение столбиком.
Следующий пример демонстрирует возможность использования
рекурсии в условиях.
Задача о назначении. Жители поселка N создали несколько общественных
организаций. Каждый житель может входить в несколько организаций
или ни в одну. Имеется список членов каждой организации.
Требуется выбрать в каждой организации председателя так, чтобы никто
не занимал двух постов сразу.
Формально: дан список термов (общественных организаций), каждый
из которых есть список символов в скобках (членов организации).
Функция Assign должна построить список символов (председателей
организаций, в котором все символы различны, и на i -м месте стоит
Языки логического программированияВ.С. Рублев
59

символ из i -го терма. Если построение удастся, то результатом станет
этот список в скобках, если же решений нет, то результат - символ '*'.
Для того чтобы решить задачу, сначала ее усложним: сведем ее к
функции Assign1, которая имеет еще один аргумент - "запрещенное
множество" eZ, т. е. список символов, которые нельзя использовать.
Идея состоит в том, что символы, отобранные из части термов,
составляют "запрещенное множество" при отборе из другой части.
Assign { eA = <Assign1 () eA>; };
Assign1 {
(eZ) = (eZ);
(eZ) (e1 sA e2) eX , <Include (eZ) sA> : F,
<Assign1 (eZ sA) eX> : (eR) = (eR);
(eZ) eX = ’*’;
};
В этом решении функция Assign вызывает функцию Assign1 c
дополнительным слева пустым списком, который и должна наполнить
эта функция. Для его наполнения функция Assign1 во втором своем
рефал-предложении исследует очередной терм (очередной
организации), и если в этом терме найдется символ sA, не входящий в
список sZ (первое условие), то этот символ включается в список
вторым условием, но только в том случае, когда такое назначение не
помешает назначениям для последующих термов, для чего во втором
условии делается рекурсивный вызов Assign1. Если решение задачи
существует, то второе рефал-предложение сформирует полностью
список sZ и, вызвав первое рефал-предложение, закончит работу
благополучно, возвратив список sZ (интерпретация переменной eR
может быть любая - например, пустое выражение). Если же решения не
существует, то для какого-либо очередного терма не найдется решения
во втором рефал-пр едложении, и функция закончит работу, вызвав
третье рефал-предложение (возврат символа '*').
Анализ приведенного решения показывает, что здесь имеет место
полный перебор возможных назначений: если имеется n организаций
по m членов, то трудоемкость алгоритма - . В то же время для
такой задачи о назначениях существует алгоритм трудоемкости
Языки логического программированияВ.С. Рублев
60
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
