Внутренняя база данных
4.5. Внутренняя база фактов
Динамическая внутренняя база данных состоит из фактов, поэтому ее еще называют базой фактов. Она может загружаться из файла на диске и записываться в файл. Ниже рассматриваются предикаты, предназначенные для работы с базой данных.
Объявление базы фактов
Внутренняя база данных объявляется следующим образом:
class facts - rel
супруг: (string Муж, string Жена).
База данных имеет имя rel. Имена внутренних баз данных принадлежат домену factDB. База данных может не иметь имени. В имплементации одного и того же класса может быть несколько безымянных внутренних баз данных. Но если требуется загружать базу данных из файла или сохранять ее в файле, она должна иметь имя.
Добавление фактов
Предикат asserta добавляет факт в начало базы данных:
asserta(супруг(Муж, Жена)).
Предикаты assert и assertz добавляют факт в конец базы данных:
assert(супруг(Муж, Жена)).
В добавляемых фактах должны быть определены значения всех полей.
Удаление фактов
Предикат retract удаляет первый факт, который унифицируется с аргументом этого предиката. Этот предикат принимает значение ложь, если факт, который требуется удалить, отсутствует в базе данных.
Предикат retract может возвращать значения, хранящиеся в удаляемых фактах. Например, цикл
retract(супруг(Муж, _)), write(Муж), nl, fail.
удаляет все факты для предиката супруг, при этом печатаются имена мужей.
Предикат retractAll удаляет все факты, которые унифицируются с аргументом этого предиката. Этот предикат всегда успешный, значения аргументов он не возвращает:
retractAll(супруг(Муж, _)).
Предикат retractFactDb удаляет все записи из базы данных:
retractFactDb(rel).
Загрузка базы данных из файла и запись в файл
Сохранение в файл фактов базы данных выполняет предикат save:
file::save("family.txt", rel)
Если указанного файла нет, то он будет создан.
Предикат consult загружает факты базы данных из файла, при этом они добавляются к текущему состоянию базы данных:
file::consult("family.txt", rel)
Предикат reconsult заменяет текущее состояние базы данных фактами из файла:
file::reconsult("family.txt", rel)
Следующая программа — это пример работы с внутренней базой данных, изменения в которую вносятся во время исполнения программы посредством диалога с пользователем. Для организации диалога применяется repeat-цикл. Выход из цикла производится только при выборе пользователем команды "Выход". Для обработки ошибок используется конструкция try-catch.
Программа представляет собой телефонную записную книжку. Пользователь может просмотреть текущее состояние базы данных, а также добавить или удалить сведения об именах и номерах телефонов. Особое внимание уделяется сообщениям об изменениях в базе данных.
class facts - phones
phone: (string Имя, string НомерТелефона).
class predicates
load: (string FileName).
maindialog: ().
act: (string НомерДействия) determ.
add: (string Name, string Phone).
delete: (string Name).
delete: (string Name, string Phone).
ask: (string Name [out], string Phone [out]) determ.
ask: (string Name [out]) determ.
printDb: ().
clauses
load(FileName):-
file::existFile(FileName),
!,
try file::consult(FileName, phones) % обработка ошибок
catch Error do
writef("Error %. Unable to load the database from "
"the file %\n", Error, FileName)
end try;
succeed().
maindialog():-
std::repeat(),
write("\nВыберите действие:\n",
"\t1. Просмотр базы данных\n",
"\t2. Добавить запись\n",
"\t3. Удалить запись\n",
"\t4. Выход\n"),
X = readLine(),
act(X),
!.
maindialog().
act("1"):- !,
write("\nВсе записи\n"),
printDb(),
fail.
act("2"):- !,
write("\nДобавление записи"),
ask(Name, Phone),
add(Name, Phone),
fail.
act("3"):- !,
write("\nУдаление записей. Выберите действие:\n",
"\t3.1 Удаление одной записи\n",
"\t3.2 Удаление всех записей\n"),
X = readLine(),
act(X).
act("3.1"):- !,
printDb(),
ask(Name, Phone),
delete(Name, Phone),
fail.
act("3.2"):- !,
ask(Name),
delete(Name),
fail.
act("4"):- !, write("\nВыход.\n").
act(_):-
X = readLine(),
act(X).
printDb():-
phone(Name, Phone),
writef(" %-20%\n", Name, Phone),
fail;
succeed().
ask(Name, Phone):-
ask(Name),
write("Введите номер: "),
Phone = string::trim(readLine()).
ask(Name):-
write("\nВведите имя: "),
Name = string::trim(readLine()),
Name <> "".
add(Name, Phone):-
phone(Name, Phone),
!,
writef("Запись % - % уже есть в базе данных\n",
Name, Phone);
assert(phone(Name, Phone)),
writef("Запись '% - %' добавлена\n", Name, Phone).
delete(Name):-
not(phone(Name, _)),
!,
writef("Записей для '%' нет\n", Name);
retractall(phone(Name, _)),
writef("Записи для '%' удалены\n", Name).
delete(Name, Phone):-
retract(phone(Name, Phone)),
!,
writef("Запись '% - %' удалена\n", Name, Phone);
writef("Записи '% - %' нет в базе данных\n", Name, Phone).
run():-
setConsoleTitle("Телефоны"),
FileName = "phones.txt",
load(FileName),
maindialog(),
file::save(FileName, phones),
_ = readLine().
Пример
4.3.
База данных "Телефоны"
Файл "phones.txt" создается в директории Exe проекта. Предикат setConsoleTitle устанавливает заголовок окна консоли, предикат writef выполняет форматированный вывод. Предикат trim удаляет пробельные символы в начале и в конце строки. Предикат existFile проверяет, существует ли указанный файл.
Упражнение 2. Добавьте в программу про телефоны (листинг 4.3) поиск номеров телефонов по имени и поиск имени по номеру телефона.