Теория
Обычно программа на Прологе состоит из четырех основных программных разделов. К ним относятся:
раздел clauses (предложений);
раздел predicates (предикатов);
раздел domains (доменов);
раздел goal (целей).
Раздел clauses - это сердце Пролог-программы; именно в этот раздел записываются факты и правила, которыми будет оперировать Пролог, пытаясь разрешить цель программы.
Раздел predicates - это раздел, в котором объявляются предикаты и домены (типы) их аргументов.
Раздел domains служит для объявления всех используемых нами доменов, не являющихся стандартными доменами Пролога.
Раздел goal - это раздел, в который вы помещаете цель Пролог-программы.
Базы данных
Предикаты БД в Turbo Prolog описываются в разделе database, который должен располагаться перед разделом predicates. Все утверждения с предикатами, описанными в database, составляют динамическую БД.
БД называется динамической, так как во время работы программы у нее (БД) можно удалять любые содержащиеся в ней утверждения, а также добавлять новые. Динамическая БД (ДБД) может быть записана на диск и считана с него в оперативную память. В ДБД содержатся только факты, но не правила. В статической базе данных утверждения представлены фактами и являются частью кода программы.
Встроенные предикаты для работы с БД
Для добавления в базу данных используются следующие предикаты:
asserta(fact) // добавление в начало базы данных dbasedom
asserta(fact, dbaseName) // добавление в начало базы данных dbaseName
assertz(fact) // добавление в конец базы данных dbasedom
assertz(fact, dbaseName) // добавление в конец базы данных dbaseName
Загрузка фактов из файла:
consult(fileName) // загрузка в базу данных dbasedom
consult(fileName, dbaseName) // загрузка в базу данных dbaseName
Удаление факта:
retract(fact) // удаление из базы данных dbasedom
retract(fact, dbaseName) // удаление из базы данных dbaseName
Сохранение базы данных в файле:
save(fileName) // сохранение базы данных dbasedom в файле fileName
save(fileName, dbaseName) // сохранение базы данных dbaseName в файле fileName
Описание программы
База данных будет содержать одну таблицу — таблицу футболок, столбцы которой перечислены далее:
Name — название.
Price (RUB) — цена в рублях.
Sex — мужская или женская футболка.
Size (International) — международный размер футболки.
Size (Europe) — европейский размер футболки.
Color — цвет.
Material — материал футболки.
Production year — год производства.
Предполагаются следующие возможности программы:
Считывание базы данных из CSV-файла
Добавление новой записи в базу данных;
Удаление футболки по названию;
Изменение записи в базе данных;
Поиск футболки по названию;
Отображение всех футболок в консоли;
Сохранение базы данных в CSV-файл;
Удаление всех записей из базы данных;
Выход из программы;
Код
В самом начале идёт описание всех используемых в программе доменов, баз данных и предикатов:
Domains
name, world, color, sex, material = string % домены строкового типа
europe, year, price = integer % домены целочисленного типа
file = datafile % домен типа file
arr = string* % массив из строк
integers = integer* % массив из целых чисел
Database
dt_shirt(name, price, sex, world, europe, color, material, year) % описание предиката БД
Predicates
repeat % зацикливает кусок кода
do_mbase % создаёт главное окно программы и вызывает предикат menu
menu % создаёт меню программы
process(integer) % ждет ввода номера функции из меню и затем вызывает её
clear_database % очищение базы данных
error % сообщает об ошибке
read_until_not_integer(integer) % ждет ввода целого числа
write_all % вывод всех футболок в командную строку
write_all(arr,integers,arr,arr,integers,arr,arr,integers) % вывод всех футболок в командную строку
write_all_csv % запись всех футболок в csv файл
write_all_csv(arr,integers,arr,arr,integers,arr,arr,integers) % запись всех футболок в csv файл
read_rows() % считывает строки из csv файла
front_string(string, string, string) % считывание одного значения из csv до разделителя ;
read_prov(integer) % ввод номера функции из меню
find(integer) % вызов предиката поиска в зависимости от введенного числа
find_shirt_name(string) % поиск футболки по имени
find_material(string) % поиск футболки по материалу
find_name(string)
find_mat(string)
Чуть ниже находится раздел Goal, в котором содержатся предикаты, вызывающиеся при запуске программы:
Goal
do_mbase. % вызов предиката do_mbase
Ну и наконец Clauses. Здесь я опишу все правила программы.
repeat - необходимо для зацикливания. Реализуется вызовом самого себя:
repeat.
repeat:-repeat.
read_prov - при вводе целого числа возвращает это число, иначе вызывает предикат error:
read_prov(Vibor):-
readint(Vibor);
error,
Vibor = 0,
menu.
Меню
do_mbase - предикат, с которого начинается выполнение программы. Создаёт главное окно и вызывает menu.
menu - создаёт меню и ждет пока пользователь введет число от 1 до 9. При вводе числа вызывается предикат process, который выполняет одну из девяти выбранных функций:
do_mbase :-
makewindow(1,11,3," T-SHIRTS DATABASE ",0,0,25,80),
menu,
clear_database.
menu :-
repeat, clearwindow,
nl,
write(" ********************************* "),nl,
write(" * 1. Read Database from file * "),nl,
write(" * 2. Add new T-shirt in DB * "),nl,
write(" * 3. Delete T-shirt from DB * "),nl,
write(" * 4. Edit T-shirt in DB * "),nl,
write(" * 5. Find T-shirt * "),nl,
write(" * 6. Show all data * "),nl,
write(" * 7. Write Database to file * "),nl,
write(" * 8. Delete All DB * "),nl,
write(" * 9. Exit * "),nl,
write(" ********************************* "),nl,
write(" Choose number 1-9 : "),
read_prov(Vibor),nl,
process(Vibor),Vibor = 9.
Далее опишем все эти функции.
Описание функций
Считывание базы данных из csv файла
process(1) - создаёт окно, где можно ввести имя csv файла из которого мы хотим считать базу данных. Считывание происходит за счет предиката read_rows(). При успешном считывании БД выводится сообщение "DB successfully read from file.". При неудаче выводится "Error reading file!". Затем, в обоих случаях, после нажатия пробела, происходит переход обратно в меню.
process(1) :-
makewindow(2,11,3,"Read data from file",2,20,15,40),shiftwindow(2),
write("Input File name (data.csv): "),
readln(Filename),
existfile(Filename),
openread(datafile, Filename),
readdevice(datafile),
read_rows(),
closefile(datafile), readdevice(keyboard),
write("DB successfully read from file."),nl,!,
write("Press space bar"), readchar(_),
removewindow, shiftwindow(1), clearwindow, menu;
write("Error reading file!"), nl, !,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu, fail.
read_rows() — построчно считывает csv файл, при помощи восьми вызовов (у нас 8 параметров в базе данных) предиката front_string. После считывания каждой строки, вставляет полученные значения в конец базы данных dt_shirt, используя встроенный предикат assertz.
front_string(Line, Param, Tail) — считывает строку, пока не встретит csv разделитель ‑ ;
Имеет 3 параметра:
Line — начальная строка
Param — часть строки до первого разделителя ‑ ;
Tail — оставшаяся строка после разделителя ‑ ;
read_rows() :-not(eof(datafile)),
readln(Line),
front_string(Line, F1_STR, Tail1),
front_string(Tail1, F2_STR, Tail2), str_int(F2_STR, Price),
front_string(Tail2, F3_STR, Tail3),
front_string(Tail3, F4_STR, Tail4),
front_string(Tail4, F5_STR, Tail5), str_int(F5_STR, Europe),
front_string(Tail5, F6_STR, Tail6),
front_string(Tail6, F7_STR, Tail7),
front_string(Tail7, F8_STR, _), str_int(F8_STR, Year),
assertz(dt_shirt(F1_STR,Price,F3_STR,F4_STR,Europe,F6_STR,F7_STR,Year)), !, read_rows();
not(eof(datafile)), !,
write(" ********************************"), nl,
write(" * READING ERROR! * "), nl,
write(" * REMAINING DATA WAS NOT READ! * "), nl,
write(" * SOME MATERIALS ADDED! * "), nl,
write(" ******************************** "), nl; !.
front_string("", "", "") :- !.
front_string(Line, Param, Tail) :-frontchar(Line, LineH, LineT),
LineH = ';', !,
Param = "", Tail = LineT;
frontchar(Line, LineH, LineT),
LineH <> ';', !,
front_string(LineT, T, Tail),
str_char(LineHS, LineH),
concat(LineHS, T, Param).
Добавление новой футболки в базу данных
process(2) - создаёт окно, где можно ввести параметры новой футболки. Ввод числовых значений происходит через предикат read_until_not_integer. После ввода всех параметров, они вставляются в конец базы данных dt_shirt, используя встроенный предикат assertz.
process(2) :-
makewindow(3,11,3,"Add data",2,20,18,58),shiftwindow(3),
write("Please, Input T-shirt:"),nl,
write("Name: "), readln(Name),
write("Price (RUB): "), read_until_not_integer(Price),
write("Sex: "), readln(Sex),
write("Size (International) : "), readln(World),
write("Size (Europe): "), read_until_not_integer(Europe),
write("Color: "), readln(Color),
write("Material: "), readln(Material),
write("Production year: "), read_until_not_integer(Year),
assertz(dt_shirt(Name,Price,Sex,World,Europe,Color, Material,Year)),
write(Name," added to DB"), nl,!,
write("Press space bar. "), readchar(_),
removewindow, shiftwindow(1), clearwindow, menu.
read_until_not_integer - проверяет, является ли введенное значение целым числом больше 0 или нет. Если не является, то вызывается повторно.
read_until_not_integer(Integer):-
readint(Integer),
Integer >=0, !;
write("Enter integer number >=0: "),
read_until_not_integer(Integer).
Удаление футболки из базы данных
process(3) - создаёт окно, где можно ввести название футболки, которую необходимо удалить. После ввода названия футболки, она удаляется из базы данных dt_shirt, используя встроенный предикат retract.
process(3) :-
makewindow(4,11,3,"Delete data",10,30,7,40),shiftwindow(4),
write("Input T-shirt name: "), readln(Name),
retract(dt_shirt(Name,_,_,_,_,_,_,_)),
write(Name," removed from DB "), nl, !,
write("Press space bar."), readchar(_),
removewindow, shiftwindow(1);
write("No data."),nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1).
Изменение информации о футболке
process(4) - создаёт окно, где можно ввести название футболки, информацию о которой необходимо изменить. После ввода названия футболки, она удаляется из базы данных dt_shirt, используя встроенный предикат retract. Далее вводятся все парам
process(4) :-
makewindow(5,11,3,"Edit data",2,20,18,58),shiftwindow(5),
write("Input T-shirt name: "), readln(Name1),
retract(dt_shirt(Name1,_,_,_,_,_,_,_)),
write("Name: "), readln(Name),
write("Price (RUB): "), read_until_not_integer(Price),
write("Sex: "), readln(Sex),
write("Size (International) : "), readln(World),
write("Size (Europe): "), read_until_not_integer(Europe),
write("Color: "), readln(Color),
write("Material: "), readln(Material),
write("Production year: "), read_until_not_integer(Year),
assertz(dt_shirt(Name,Price,Sex,World,Europe,Color, Material,Year)),nl, !,
write("Press space bar."), readchar(_),
removewindow, shiftwindow(1);
write("No data."),nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu.
Показать всю информацию о футболке
process(5) - создаёт окно, где можно выбрать, как искать нужную футболку: 1 - по названию или 2 - по материалу. После вызывается предикат find, в котором происходит поиск футболки по выбранному нами параметру и затем выводится вся информация о ней.
process(5) :-
makewindow(6,11,3," Show T-shirt ", 2,30,22,47), shiftwindow(6),
write("1. Find T-shirt by Name "),nl,
write("2. Find T-shirt by Material "),nl,
write(" Choose number 1-2 : "),
read_until_not_integer(N),
N>0,N<3,
find(N),
write("Press space bar"), readchar(_),
removewindow, shiftwindow(1), clearwindow, menu;
write("Wrong input."),nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu.
find(1) - необходимо ввести название футболки, информацию о которой мы хотим получить. После ввода названия, вызывается предикат find_shirt_name, осуществляющий поиск футболки в базе данных. Если такое название было найдено, в консоль выводится вся информация о футболке. Если нет, то выводится сообщение "No such T-shirt in database!".
find(2) - аналогично find(1), только теперь необходимо ввести материал интересующей нас футболки.
find(1):-clearwindow, write("Input T-shirt name: "), readln(Name),
find_shirt_name(Name), find_name(Name).
find(1):-write("No such T-shirt in database!").
find(2):-clearwindow, write("Input T-shirt material: "), readln(Material),
find_material(Material), find_mat(Material).
find(2):-write("No such T-shirt in database!").
find(_):-write("Error ").
find_shirt_name(Name):-
dt_shirt(Name,Price,Sex,World,Europe,Color, Material,Year),nl,
write(" Name : ",Name),nl,
write(" Price (RUB) : ",Price),nl,
write(" Sex : ",Sex),nl,
write(" Size (International): ",World),nl,
write(" Size (Europe): : ",Europe), nl,
write(" Color : ",Color),nl,
write(" Material : ",Material),nl,
write(" Production year : ",Year),nl, nl, fail.
find_shirt_name(_).
find_material(Material):-
dt_shirt(Name,Price,Sex,World,Europe,Color, Material,Year),nl,
write(" Name : ",Name),nl,
write(" Price (RUB) : ",Price),nl,
write(" Sex : ",Sex),nl,
write(" Size (International): ",World),nl,
write(" Size (Europe): : ",Europe), nl,
write(" Color : ",Color),nl,
write(" Material : ",Material),nl,
write(" Production year : ",Year),nl, nl, fail.
find_material(_).
find_name(Name):-dt_shirt(Name,_,_,_,_,_,_,_).
find_mat(Material):-dt_shirt(_,_,_,_,_,_,Material,_).
Показать все записи в базе данных
process(6) - создаёт окно, в котором выводятся все записи базы данных на экран. Для этого используется предикат write_all.
process(6) :-
makewindow(7,11,3," Show All data ", 0,0,25,80), shiftwindow(7),
write("Name, Price(Rub), Sex, Size(Inter.), Size(Europe), Color, Material, Year"),
nl,
write("************************************************************************"),
nl,
write_all,
nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu;
write("No data."),nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu.
write_all() - использует предикат findall(X,P,L), который собирает в список L все объекты X, удовлетворяющие цели P.
write_all([P1|T1], [P2|T2], [P3|T3], [P4|T4], [P5|T5], [P6|T6], [P7|T7], [P8|T8]) - выводит все значения найденные в базе данных через запятую.
write_all() :-
findall(P1, dt_shirt(P1,_,_, _,_,_,_,_), P1s),
findall(P2, dt_shirt(_,P2,_,_,_,_,_,_ ), P2s),
findall(P3, dt_shirt(_,_,P3, _,_,_,_,_), P3s),
findall(P4, dt_shirt(_,_,_,P4,_,_,_,_ ), P4s),
findall(P5, dt_shirt(_,_,_,_,P5,_,_,_ ), P5s),
findall(P6, dt_shirt(_,_,_,_,_,P6,_,_ ), P6s),
findall(P7, dt_shirt(_,_,_, _,_,_,P7,_), P7s),
findall(P8, dt_shirt(_,_,_, _,_,_,_,P8), P8s),
write_all(P1s, P2s, P3s, P4s, P5s, P6s, P7s, P8s);
writedevice(screen).
write_all([], [], [], [], [], [], [], []) :- !.
write_all([P1|T1], [P2|T2], [P3|T3], [P4|T4], [P5|T5], [P6|T6], [P7|T7], [P8|T8]) :-
write(P1,", ",
P2," (RUB), ",
P3,", ",
P4,", ",
P5,", ",
P6,", ",
P7,", ",
P8),nl,
write("------------------------------------------------------------------------"),nl,
write_all(T1, T2, T3, T4, T5, T6, T7, T8).
Записать базу данных в файл csv
process(7) - создаёт окно, в котором необходимо ввести название файла для сохранения базы данных. После записывающее устройство ставится на файл - writedevice(datafile) и вызывается предикат write_all_csv, записывающий базу данных в файл.
process(7) :-
makewindow(8,11,3," Write Database to file ", 7,30,12,47), shiftwindow(8),
write("Input file name (data.csv): "),
readln(Filename),
existfile(Filename), % Существует ли файл
openwrite(datafile, Filename),
writedevice(datafile),
write_all_csv,
closefile(datafile),
writedevice(screen),
write("DB successfully written to file."),
nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu;
write("Error writing file!"),nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu.
write_all_csv() - устроен аналогично write_all(), только вместо запятых, параметры футболки разделяются символом - ;. Это необходимо для корректной записи в csv файл.
write_all_csv() :-
findall(P1, dt_shirt(P1,_,_, _,_,_,_,_), P1s),
findall(P2, dt_shirt(_,P2,_,_,_,_,_,_ ), P2s),
findall(P3, dt_shirt(_,_,P3, _,_,_,_,_), P3s),
findall(P4, dt_shirt(_,_,_,P4,_,_,_,_ ), P4s),
findall(P5, dt_shirt(_,_,_,_,P5,_,_,_ ), P5s),
findall(P6, dt_shirt(_,_,_,_,_,P6,_,_ ), P6s),
findall(P7, dt_shirt(_,_,_, _,_,_,P7,_), P7s),
findall(P8, dt_shirt(_,_,_, _,_,_,_,P8), P8s),
write_all_csv(P1s, P2s, P3s, P4s, P5s, P6s, P7s, P8s);
writedevice(screen).
write_all_csv([], [], [], [], [], [], [], []) :- !.
write_all_csv([P1|T1], [P2|T2], [P3|T3], [P4|T4], [P5|T5], [P6|T6], [P7|T7], [P8|T8]) :-
write(P1,"; ",
P2,"; ",
P3,"; ",
P4,"; ",
P5,"; ",
P6,"; ",
P7,"; ",
P8),nl,
write_all_csv(T1, T2, T3, T4, T5, T6, T7, T8).
Удаление всех записей из базы данных
process(8) - создаёт окно, в котором при успешном удалении всех записей из базы данных, выведется сообщение "DB has been cleared". При ошибке выведется "Error writing file!".
process(8) :-
makewindow(9,11,3," Delete All DB ",10,30,7,40), shiftwindow(9),
clear_database,
write("DB has been cleared"),
nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu;
write("Error writing file!"),nl,!,
write("Press space bar."),readchar(_),
removewindow, shiftwindow(1), clearwindow, menu.
clear_database - удаляет все факты из базы данных, используя встроенный предикат retract:
clear_database:-
retract(dt_shirt(_,_,_,_,_,_,_,_)),
fail.
clear_database:-!.
Выход из программы
process(9) - очищает базу данных и выводит сообщение "See you again! ".
process(9) :-
clear_database,
write("See you again! "),readchar(_),exit.
Конец
Вот вроде и все.
Надеюсь вы нашли что искали)
Ссылка на исходный код: https://github.com/KirillTaE/Dynamic_DataBase_on_TurboProlog
Комментарии (12)
18741878
00.00.0000 00:00+4Судя по картинкам, автор использует Turbo Prolog года этак 1993/1994 (плюс-минус пара лет). На чем запускали? Неужели сохранился компьютер тех лет с MS-DOS?
KirillTaE Автор
00.00.0000 00:00+2Для запуска сего чуда использовал эмулятор DOS - DOSBox.
18741878
00.00.0000 00:00+2speshuric
00.00.0000 00:00+1Книга напомнила интересный опыт на Прологе. Год этак 2000-2001 или около того. Конец весны. Зашёл в выходной к знакомому в общежитии (жили на соседних этажах, совершенно небогато и по студенчески), а он в печали. Я спросил его, чего он такой невесёлый, на что он ответил, что надо сдавать курсовую и зачёт по Прологу, а он его не понимает. Я Пролог понимаю, но понимаю, что понять пролог за выходные э... хм... сложновато. Но мне заняться особо было нечем в тот день и я сказал:
-- Окей, давай помогу. Но тут без поллитры не разобраться.
Молча знакомый достаёт бутылку водки.
-- А закусить есть чем?
Достаёт кубик бульона.
-- Только это.
-- А хоть запить?
-- Есть вода в чайнике.
...
Но разобрались.
heiheshang
00.00.0000 00:00+2Обычно программа на Прологе состоит из четырех основных программных >>разделов. К ним относятся:
Я бы так про Пролог не писал - это касается Turbo Prolog, VIP-Prolog конкретных реализаций пролога, обычная прграмма на прологе состоит из предикатов. Посмотрите на SWI-Prolog. Прочитал статью прям в юность вернулся, столько лет прошло, а я до сих пор на прологе пишу.
MAXH0
В свое время Пролог был очень популярен, но как-то не осилил вызовы нового дня.
KirillTaE Автор
Ну а мне с программой моего ВУЗа пришлось его осилить) Изучал данный язык в 2022 году....
MAXH0
Интересно! Экспертные системы?
KirillTaE Автор
Предмет - Логическое и функциональное программирование. Направление - Программная инженерия
artyomsoft
Вспомнились 90е, институт. Кроме Пролога мы еще один экзотический язык изучали под названием GPSS
Я понимаю ностальгию вашего преподавателя по тем временам. Но сейчас есть более современный SWI Prolog
torbasow
1990-е, да. Мы официально не изучали, но было интересно, я изучил сам, и сдавал на нём курсовую.