Внутренняя база данных
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) поиск номеров телефонов по имени и поиск имени по номеру телефона.