- •Введение
- •1. Составление программ
- •1.1. Основные определения
- •1.2. Декларации в программах
- •1.3. Объявление предикатов и типов их аргументов
- •1.4. Другие разделы программы
- •Упражнения
- •2. Механизмы доказательства правил
- •2.1. Сопоставление с откатом
- •2.2. Рекурсия
- •Упражнения
- •3.Операции в Visual Prolog. Ввод-вывод
- •3.1. Операции
- •3.2. Предикаты ввода-вывода
- •4. Управление процессом доказательства правил
- •4.1. Искусственный откат
- •4.2. Отсечение
- •4.3. Повтор, определяемый пользователем
- •5. Списки
- •5.1. Процедуры обработки списков
- •5.2. Организация стеков и очередей
- •6. Внутренняя база фактов
- •7. Иерархическая организация данных
- •8. Работа с деревьями и графами
- •8.1. Двоичные деревья
- •8.2. Графы
- •9. Работа с именами и строками
- •Заключение
- •Библиографический список
- •Оглавление
5.2. Организация стеков и очередей
В Прологе, как и в процедурном программировании, списки могут служить основой для построения стеков и очередей.
Начнем со стека. Известно, что для него должны быть определены три операции: добавление элемента в вершину стека, выталкивание элемента из вершины, проверка стека на наличие элементов.
Рассмотрим следующий предикат:
stack(Top, Stack, [Top|Stack]) % ( i, i,o) (o, o ,i)
Здесь Top – элемент, находящийся в вершине стека, второй и третий аргументы – списки, выражающие состояние стека до и после выполнения операции.
Предположим, стек работает с целыми числами и текущее состояние его [1, 2, 3]. Тогда третий аргумент конкретизируется списком с новым состоянием стека.
stack(10, [1,2,3], R).
R=[10,1,2,3]
Предикат, в котором первые два аргумента свободны, а третий конкретизирован текущим состоянием стека, выдаст следующее решение
stack( T, S, [ 8, 2, 13 ] ).
T= 8, S= [ 2, 13]
Проверка стека на наличие элементов выполняется предикатом
empty_stack( [ ] ).
Таким образом, организация стека на основе списка несложна. Чуть более сложной является организация очереди, где новый элемент заносится в ее конец. Для этого воспользуемся рекурсивной процедурой queue ( E, Q, Qnew), где
Q – список – текущее состояние очереди,
Qnew – список-очередь после добавления элемента E.
queue( E, [ ], [ E ] ).
queue( E, [ H|T ], [ H|Tnew ] ):- queue( E, T, Tnew).
Рекурсивные вызовы продолжаются до тех пор, пока текущая очередь не станет пустой. Терминальный предикат обеспечит создание третьего списка с одним этим элементом. Во время подъема рекурсии запомненные элементы очереди будут последовательно заноситься в голову этого списка.
Считывание 1-го элемента можно реализовать с помощью предиката
dequeue(E, [E|T], T).
Программа для реализации обеих структур выглядит следующим образом:
DOMAINS
list=integer*
PREDICATES
% стек
empty_stack(list).
stack(integer, list, list).
%очередь
empty_queue(list).
enqueue(integer, list, list).
dequeue(integer, list, list).
CLAUSES
% стек
empty_stack([]).
stack(Top, Stack, [Top|Stack]).
% очередь
empty_queue([]).
enqueue(E, [], [E]).
enqueue(E, [H|T], [H|Tnew]):- enqueue(E, T, Tnew).
dequeue(E, [E|T], T).
GOAL
stack(T, S, [1,2,3]). % T=1 S=[2, 3]
stack(0, [1,2], R). % R=[0, 1,2]
enqueue(1, [0,5], R). %R=[1,5,0]
S=[1,2,3], dequeue (X, S, T).% X=1 T=[2,3]
6. Внутренняя база фактов
До сих пор мы рассматривали факты как часть исходной программы. Между тем, в Прологе имеется возможность добавлять факты в программу и удалять их в процессе ее выполнения. Шаблон таких фактов должен быть объявлен в разделе facts (устаревшее, но еще действующее в VIP название этого раздела – database) , который должен располагаться перед разделом предикатов. Факты из этого раздела хранятся в оперативной памяти отдельно от остальных предложений программы и образуют так называемую внутреннюю базу данных. (Кроме внутренней, в VIP может быть организована и внешняя база данных [5].) Действия над фактами, хранящимися во внутренней базе, осуществляются так же, как и над фактами исходной программы, но операции над ними выполняются быстрее. Иногда внутреннюю базу данных называют динамической. До начала работы программы внутренняя база фактов пуста.
Стандартные предикаты для добавления и удаления информации из базы следующие:
asserta(Predicat) – заносит новый факт во внутреннюю базу и помещает его перед имеющимися фактами этой базы;
assertz(Predicat) – заносит новый факт в динамическую БД и помещает его после всех имеющихся там фактов.
(Так же, как assertz действует предикат assert.)
Пример.
FACTS
студент(string, string)
PREDICATES
/* . . . */
asserta(студент("Сидоров","А-87")).
Объект Predicat, являющийся параметром предикатов asserta и assertz, может быть определен в разделе clauses либо в явном виде, либо может быть сформирован на основе данных, извлеченных из предикатов раздела clauses.
Пример. Из программы, содержащей сведения о студентах, поместить во внутреннюю базу факты, содержащие информацию только о студентах, имеющих хотя бы одну двойку:
DOMAINS
фамилия, группа, предмет=string
отметка=integer
FACTS
двоечник(фамилия, группа)
PREDICATES
студент (фамилия, группа, предмет, отметка)
CLAUSES
студент("Петров","А-97","ООП",3).
...
добавить_к_БД :- студент(Name, Grupp, Subject,
Ot), Ot=2, assertz (двоечник(Name, Grupp)), fail.
добавить_к_БД:-!.
GOAL
добавить_к_БД.
(Правило «добавить_к_БД:-!.» необязательно и введено только для того, чтобы одноименное правило было доказанным; если его не будет, то из-за наличия fail правило всегда будет неуспешным.)
Предикат retract(Predicat) используется для удаления фактов из базы. Рассмотрим следующие применения предиката:
- удалить первый факт базы:
retract(двоечник("Сидоров", "А-87")).
- удалить все утверждения, связанные с конкретным предикатом:
удалить_из_DB:- retract(двоечник(_, _, _)), fail.
- полностью очистить содержимое базы:
удалить_DB :- retract( _ ), fail.
(В последнем случае целесообразно применять системный предикат retractall.)
Для внутренней базы фактов предусмотрена возможность обмена с дисковой памятью.
Предикат save(FileName) записывает все факты, содержащиеся в базе, в текстовый файл с именем FileName.
Предикат consult(FailName) загружает в оперативную память все факты, хранящиеся в файле с именем FileName. Предикат считывает данные из файла в том же формате, в котором они были занесены предикатом save.
Пример. Из файла auto.txt, хранящего факты об автомобилях, таких как в программе подразд. 3.2, выбрать все записи и поместить их во внутреннюю базу, исправить значение аргумента «Год» в каждой записи и перенести исправленную информацию в новый файл.
DOMAINS
марка= symbol
год= integer
FACTS
auto(марка,год)
PREDICATES
edit
run
CLAUSES
edit :- auto(Name, Year),
write(Name), write(Year),
retract(auto(Name,Year)),
write("Введите новыйгод"), nl, readint(X), asserta(auto(Name,X)), fail.
edit:-!.
run:-consult("d:\\auto.txt"),edit,
save("d:\\auto1.txt").
GOAL
run.
В программе может быть несколько различных внутренних баз. В этом случае каждая из них должна быть поименована, например
facts – mydatabase1.
Наличие нескольких баз приводит к необходимости использовать предикаты assert, retract, consult и save не с одним, а с двумя аргументами; второй – имя соответствующего раздела.
assert(…., mydatabase1).
Отметим, что если в программе существует одна безымянная база фактов, то она должна быть объявлена с ключевым словом global:
GLOBAL FACTS
