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

Функциональное и логическое программирование. Практикум

.pdf
Скачиваний:
0
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
writedevice(myfile), /* Перенаправление вывода в файл*/ readln(String), /* Ввод с клавиатуры строки*/
write_string(String), /* Запись введенной строки в файл*/ closefile(myfile). /* Закрытие файла*/
check_exist(Filename):-existfile(Filename),!, openappend(myfile,Filename). /* Открытие файла для добавления*/ check_exist(Filename):-write(“Создан новый файл”),nl, openwrite(myfile,Filename). /* Создание нового файла для записи*/ write_string(“end”):-!. write_string(String):-
write(String), nl, /* Запись строки в файл с переводом строки*/
readln(String1), /* Ввод с клавиатуры новой строки*/
write_string(String1). /* Запись новой строки в файл*/
Пример 5: Напишем предикат read_file, который выводит на экран строки
из файла, начиная с некоторого номера. Имя файла и номер строки вводятся с клавиатуры.
domains file=myfile predicates
read_file(integer) write_screen check_exist(string)
goal
write(“Введите имя файла: “), readln(Filename), /* Ввод с клавиатуры имени файла */ check_exist(Filename), /* Проверка существования файла*/ write(“Введите номер строки для вывода строк из файла на экран ”), readint(N), openread (myfile,Filename), /* Открытие файла для чтения*/ readdevice(myfile), /* Перенаправление ввода на файл*/ read_file(N), /* Пропуск в файле N-1 строки*/ write(“Содержимое файла, начиная с ”,N,”-ой строки:”),nl, write_screen, /* Вывод на экран строк, начиная с N-ой*/ closefile(myfile). /* Закрытие файла*/
clauses
check_exist(Filename):-existfile(Filename),!. check_exist(_):-write(“Такого файла нет”),nl,
fail. read_file(1):-!. read_file(N):-readln(_), /*Пропускаем строку*/ N1=N-1, read_file(N1). read_file(N):-eof(myfile),!, write(“В файле меньше, чем ”,N,” строк”),nl,
81
fail. write_screen:- eof(myfile),!. write_screen:- readln(String), /* Чтение из файла строки*/ write(String),nl, /* Вывод строки на экран и перевод строки*/ write_screen.
Иногда бывает полезен предикат
file_str(<имя физического файла >,<строка>), который считывает в указанную
строку всех символов из файла или наоборот содержимое строки записывает в файл. Размер файла должен быть не больше 64K. Если файл не существовал, то он создастся.
2.11 Динамические базы данных
Реляционная модель базы данных описывает множество отношений.
Программа на Прологе представляет собой набор фактов и правил, поэтому ее можно рассматривать как реляционную базу данных. Отношения базы данных – это предикаты, атрибутами отношений являются объекты предикатов, элементы отношений присутствуют в виде фактов (явно) и правил (неявно), мощности отношений определяются количеством фактов и правил для каждого предиката. Описанная база данных является статической, она является частью кода программы, и поэтому во время работы программы не может быть изменена. Иногда в процессе работы возникает необходимость изменить, удалить или добавить некоторые факты. Такие факты являются частью динамической базы данных. Предикаты из динамической базы данных описываются в разделе database, который находится после раздела domains. При запуске программы факты из динамической базы данных помещаются в оперативной памяти в виде специальных таблиц отдельно от кода программы, поэтому могут быть изменены во время работы программы. Следует иметь в виду, что динамическая база данных сохраняется в оперативной памяти во время всего сеанса работы с Прологом. Поэтому, если эту память не очищать после работы программы, при повторном запуске программы можно получить неожиданные результаты.
Пример: Опишем предикат person динамической базы данных:
domains
family=symbol age=integer
database
person(family,age)
Отметим особенности динамической базы данных:
1. предложения динамической базы данных могут быть только фактами;
2. факты не могут содержать свободных переменных;
3. если объекты предиката динамической базы данных относятся к типу
symbol, то они должны быть заключены в кавычки;
82
4. имена предикатов динамической базы данных не могут совпадать с именами
предикатов, описанных в разделе predicates;
5. имена предикатов динамической базы данных должны быть уникальны, т.е.
предикат определяется только своим именем, без учета арности .
6. предложения для динамической базы данных можно загрузить из файла,
используя предикат consult.
2.11.1 Добавление и удаление фактов
Для добавления факта в начало или конец динамической базы данных
используются соответственно предикаты: asserta(<факт>) и assertz(<факт>). Эти предикаты всегда успешны.
Для удаления факта, сопоставимого с F, используется предикат retract(F). Пример 1: Напишем предикат delete_person, который будет выводить на
экран фамилии тех людей, возраст которых больше 50 лет. Фамилия и возраст человека являются объектами предиката person динамической базы данных. После вывода на экран фамилии, соответствующий факт удаляется из динамической базы данных.
domains
family=symbol age=integer
database
person(family,age)
predicates
delete_person
clauses
delete_person:-person(Family,Age), Age>50, write(Family),nl, retract(person(Family,Age)), fail. delete_person.
Для модификации динамической базы данных можно использовать
предложения, содержащие предикаты asserta, assertz и retract. Например, чтобы отредактировать имеющееся в динамической базе данных утверждение, в программе должно составиться отредактированное утверждение, удалиться из базы данных старое утверждение и занестись новое.
Для удаления всех фактов, сопоставимых с F, используется предикат
retractall(F). Этот предикат всегда успешен, но в отличие от retract(F), не позволяет получить значения объектов из удаленных файлов.
Пример 2: Для удаления из динамической базы данных примера2 всех
фактов, касающихся 20-летних людей, можно воспользоваться предикатом:
retractall(person(_,20)).
83
Для полной очистки динамической базы данных используется retractall(_).
2.11.2 Заполнение динамической базы данных фактами из файла, сохранение динамической базы данных в файле
Предикат consult(<имя физического файла>) считывает из файла факты, относящиеся к предикатам, описанных в разделе database, и добавляет их в конец динамической базы данных (аналогично assertz). В случае, если файл будет содержать что-нибудь отличное от фактов динамической базы данных, предикат consult завершится неуспешно и факты из файла в динамическую базу данных добавлены не будут. Имя файла заключается в кавычки. Файлы, содержащие информацию для динамической базы данных, имеют расширение
dba.
Сохранение динамической базы данных в файле обеспечивает предикат save(<имя физического файла>). При этом если указанный файл уже существовал, то он создается заново.
Для просмотра содержимого дополнительных файлов, можно открыть
второе окно редактора, перейдя в основное окно редактора и нажав функциональную клавишу F8. Для редактирования в новом окне используются те же комбинации клавиш, что и для основного окна редактора.
Пример 1: Определим предикат multiply, который вычисляет все возможные произведения чисел от 1 до 9 (таблица умножения). Таблицу умножения сохраним в файле.
domains
list=integer*
database
multiply(integer,integer,integer) count(integer) /* Счетчик количества записей в файл*/
predicates
choice_el(integer,list) /* Выбирает из списка значение множителя */ write_table
goal
retractall(_), /* Очистка динамической памяти */ asserta(count(0)), /* Начальное значение счетчика равно 0 */ write_table, retract(count(_)), /* Удаление значение счетчика из динамической базы данных */ save("tabl.dba"), /* Сохранение содержимого динамической базы данных в
файле */ retractall(_). /* Очистка динамической памяти */
clauses
choice_el(X,[X|_]). choice_el(X,[_|Tail]):-choice_el(X,Tail). write_table:-L=[1,2,3,4,5,6,7,8,9],
84
choice_el(X,L), /* Выбирает из списка значение первого множителя */ choice_el(Y,L), /* Выбирает из списка значение второго множителя */ Z=X*Y, assertz(multiply (X,Y,Z)), /* Добавление факта в конец базы данных */ count(N), /* Чтение значения счетчика из динамической базы данных */ N1=N+1, /* Новое значение счетчика */ retract(count(N)), /* Удаление старого значения счетчика из динамической базы данных */ asserta(count(N1)), /* Добавление нового значения счетчика в динамическую базу данных */ N1=81. /* Проверка последней записи */
Как видно из примера, в динамической базе данных можно накапливать
уже вычисленные ответы на вопросы.
Пример 2: Формирование динамической базы данных «Читатель
библиотеки» с клавиатуры и сохранение ее в файле.
domains
фамилия=string число,номер_билета=integer месяц=string дата_посещ=дата_посещ(число,месяц)
database
читатель(фамилия,номер_билета,дата_посещ)
predicates
повтор запись_в_базу ответ(char)
goal
clearwindow, write("Формирование б.д."),nl, retractall(_), /* Очистка динамической памяти */ повтор, write("Будете вводить новые факты? Y/N"), readchar(A), /* Ввод ответа, на экране не отображается */ upper_lower(Answer,A),nl, /* Преобразование маленькой буквы в большую*/ ответ(Answer), /* Определяет дальнейшую работу программы */ save("reader.dba"), /* Сохранение содержимого динамической базы данных в файле */ retractall(_). /* Очистка динамической памяти */
clauses
повтор. повтор:-повтор. ответ('N'). ответ('Y'):-запись_в_базу, fail.
85
ответ('_'):-fail. /* Если введен неверный ответ, повторяется вопрос */ запись_в_базу:-write("Фамилия: "),
readln(Name),
write("Номер билета: "),
readint(Number),
write("Число посещения: "),
readint(Data),
write("Месяц посещения: "),
readln(Mounth), asserta(читатель(Name,Number,дата_посещ(Data,Mounth))). /* Сохранение введенной записи в динамической базе данных */
Пример 3: Определение количества читателей, посетивших библиотеку в
марте, по информации, находящейся в файле reader.dba, сформированном в примере 2.
domains
фамилия=string число,номер_билета=integer месяц=string дата_посещ=дата_посещ(число,месяц)
database
читатель(фамилия,номер_билета,дата_посещ) счетчик(integer)
predicates
счет
goal
consult("reader.dba"), asserta(счетчик(0)), счет, счетчик(N), write("Число читателей= ",N), retractall(_).
clauses
счет:-читатель(_,_,дата_посещ(_,”март”)), счетчик(N), N1=N+1, retract(счетчик(N)), asserta(счетчик(N1)), fail. счет.
2.12 Экранные окна и создание меню
Окно – это прямоугольная область на экране, ограниченная рамкой. Окно
можно создать с помощью предиката
makewindow(WN, ScrAttr, FrAttr, Caption, Row, Col, Height, Width), где
86
WN – целое число, определяет номер окна (нумерация начинается с 1); это число используется для в качестве ссылки в предикате gotowindow и др. ScrAttr – целое число, определяет атрибут окна, который является суммой трех чисел (цвет текста, цвет фона, мерцание). Цвет текста может изменяться от 0 до 15, цвет фона – от 0 до 112 с шагом 16, мерцание – число
128.
FrAttr – целое число, определяет атрибут рамки. Если рамки нет, то атрибут равен 0. Значения от 1 до 8 соответствуют разным цветам рамки, а значения от -8 до -1 соответствуют разным цветам мерцающей рамки.
Caption – строка, определяет текст заголовка окна в верхней части рамки. Если строка пустая, то заголовок отсутствует. Row, Col - целые числа, определяют строку и столбец верхнего левого угла создаваемого окна. В текстовом режиме на экране 25 строк и 80 столбцов. Нумерация строк идет слева направо, а столбцов – сверху вниз. Height, Width - целые числа, высота и ширина окна, включая рамку.
При создании окно заполняется цветом фона, а курсор устанавливается в
его верхний левый угол.
Пример 1: makewindow(1,4,1,“Menu”,4,20,16,40) создаст окно с
номером 1, заголовком Menu. Цвет текста в окне будет красным, фон окна ­черный (4=4+0+0), рамка – синего цвета (1). Левый верхний угол окна расположен в 4-ой строке и 20-ом столбце экрана, окно занимает 16 строк и 40 столбцов.
Если все аргументы предиката makewindow являются неозначенными
переменными, то им присваивается значение текущего окна.
Когда окно создано, оно становится активным, и вся выводимая
информация будет направляться в активное окно.
При помощи предиката shiftwindow(WN) активным может быть назначено
другое окно. При этом курсор устанавливается в позицию, в которой он был в момент предыдущего обращения к этому окну. Если аргумент предиката неозначен, то определяется номер активного окна.
Для более быстрого переключения между окнами, содержащими большое
количество текста, используется предикат gotowindow(WN). Этот предикат не сохраняет содержимое активного окна и не обновляет содержимое нового активного окна из буфера, поэтому при использовании этого предиката окна должны быть неперекрывающимися.
Предикат removewindow удаляет активное окно, при этом активным
становится окно, активизированное непосредственно перед удаляемым. При удалении окна содержимое экрана «за окном» автоматически восстанавливается.
Предикат clearwindow, который ранее использовали для очистки экрана,
очищает так же активное окно.
Для перемещения курсора в нужную позицию окна можно использовать
предикат cursor(Row,Col), где Row,Col – координаты курсора относительно
87
верхнего левого угла окна. В случае неозначенности аргументов предиката, возвращаются координаты текущего положения курсора в активном окне.
Для ввода и вывода текста в окна используются уже известные предикаты:
write, nl, readchar, readint, readln, readreal, readterm. Ввод и вывод текста осуществляется в активное окно в текущую позицию курсора.
Для задержки окна до нажатия любой клавиши можно в программе после
активации окна добавить цели:
write(“Для продолжения нажмите любую клавишу”), readchar(_),
……………
Пример 2: Напишем предикат show_menu, который создает окно с
главным меню, состоящим из трех пунктов. При выборе пользователем соответствующего пункта меню, появляется дочернее окно, в котором выполняются необходимые действия. По окончанию действий дочернее окно закрывается и снова можно выбирать пункты главного меню до тех пор, пока не будет выбран выход.
predicates
repeat process(integer) show_menu
goal
show_menu.
clauses
repeat. repeat:-repeat. show_menu:-repeat, makewindow(1,7,7,“Menu”,4,10,16,36),nl, write(“1 – процесс1”),nl, write(“2 – процесс2”),nl, write(“3 – выход”),nl,nl, write(“Введите Ваш выбор: (1-3)”), readint(X), X<4, process(X), X=0,!.
process(3).
process(1):- makewindow(2,7,7,“Процесс1”,12,36,10,36),
………………
write(“Для продолжения нажмите любую клавишу”),
readchar(_),
removewindow. process(2):- makewindow(3,7,7,“Процесс2”,10,40,10,36), ………………
write(“Для продолжения нажмите любую клавишу”),
88
readchar(_),
removewindow.
2.13 Операции над структурами данных
2.13.1 Деревья
Списки часто используют для представления множеств. Но такое представление множеств имеет недостаток, заключающийся в том, что процедура проверки принадлежности элемента множеству оказывается неэффективной для множеств с большим количеством элементов. Если искомый элемент находится в конце списка или отсутствует в списке, процедуре придется просмотреть весь список. Для увеличения эффективности процедуры поиска применяют представление множеств в виде древовидных структур. Рассмотрим одну из таких структур – двоичное дерево.
Дадим рекурсивное определение двоичного дерева. Двоичное дерево либо пусто, либо состоит из трех частей: корень, левое поддерево, правое поддерево. Корень может быть чем угодно, а поддеревья должны быть деревьями. Удобно определить двоичное дерево как функтор с тремя аргументами: корень и два поддерева и ввести обозначение для пустого дерева: nil. Тогда в программе можно описать следующий домен:
domains tree=tree(root,tree,tree); nil
Пример 1 Напишем предикат, проверяющий принадлежность элемента дереву. Дадим декларативное определение предиката. Элемент либо является корнем дерева, либо принадлежит левому поддереву, либо принадлежит правому поддереву. В программе опишем следующее двоичное дерево:
Рисунок 17 – Дерево примера 1
domains
tree=tree(root,tree,tree); nil root=integer
predicates
member_tree(integer,tree) answer(integer,tree)
89
goal
Tree=tree(5, tree(4, tree(8, tree(6,nil,nil), tree(2,nil,nil)), tree(3,nil,nil)), tree(1, tree(7,nil,nil), nil)), write("Введите элемент: "), readint(X), answer(X,Tree).
clauses
answer(X,Tree):-member_tree(X,Tree),!, write("Да"). answer(_,_):-write("Нет"). member_tree(X,tree(X,_,_)). member_tree(X,tree(_,Left,_)):-member_tree(X,Left). member_tree(X,tree(_,_,Right)):-member_tree(X,Right).
Очевидно, что если элемент находится в самом низу дерева, процедура поиска становится такой же неэффективной, как и в случае списков. Введем отношение порядка между элементами множества. Тогда элементы множества можно упорядочить слева направо в соответствии с этим отношением. Выберем отношение «меньше» для упорядочивания элементов множества, содержащего целые числа. Двоичным справочником назовем непустое дерево, для которого выполняется:
все вершины левого поддерева меньше корня; все вершины правого поддерева больше корня; левое и правое поддеревья являются двоичными справочниками.
Пример 2: Приведенное ниже дерево является двоичным справочником:
Рисунок 18 – Двоичный справочник
Пример 3: Модифицируем предикат поиска для двоичного справочника.
Для поиска в двоичном справочнике достаточно просмотреть одно поддерево,
90
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]