Ответ в теме: Пример работы с базами данных в Prolog

      Комментарии к записи Ответ в теме: Пример работы с базами данных в Prolog отключены
#2523

Множество функций предназначено для вывода информации на экран в определенном виде. Для печати списка континентов необходимо получить их с помощью встроенного предиката findall, а затем отбросить повторы с помощью функции list_to_set:

print_continents:-
findall(Continent, state(Continent, _State, _Square, _Population, _Capital), Continents),
list_to_set(Continents, [], UniqueContinents),
write(UniqueContinents), nl.

Предикат print_state_information выводит информацию о государстве, а print_states принимает список названий государств, получает информацию о них перебирая записи базы данных и выводит информацию. Важно, что название государства в данном случае используется в качестве первичного ключа базы, т.е. при наличии нескольких государств с одним названием будет выведена информация только о том, что в БД будет располагаться выше:

  print_state_information(Continent, State, Square, Population, Capital):-
write("Continent: "), write(Continent), nl,
write("State: "), write(State), nl,
write("Square: "), write(Square), nl,
write("Population: "), write(Population), nl,
write("Capital: "), write(Capital), nl.
print_states([]):-!.
print_states([HeadState|TailStates]):-
state(Continent, HeadState, Square, Population, Capital), !,
print_state_information(Continent, HeadState, Square, Population, Capital), nl,
print_states(TailStates).

Предикат print_states_by_continent_information выводит информацию о государствах заданного континента, при этом использует механизм поиска с возвратами:

  print_states_by_continent_information(Continent):-
state(Continent, State, Square, Population, Capital),
print_state_information(Continent, State, Square, Population, Capital), nl,
fail; !.

Предикат получает единственное название континента, обращается к базе данных и извлекает первую запись о государстве, выводит ее, а затем выполняет операцию fail, т.е. правило завершается неудачей и интерпретатор пытается найти другие решения — для этого извлекает из базы данных следующую запись. Так продолжается до тех пор, пока все записи базы данных не будут перебраны, а затем, в соответствии с алгоритмом поиска с возвратами, управление перейдет коду, расположенному после оператора ИЛИ (точки с запятой), т.е. оператору отсечения (восклицательный знак), завершающему работу функции удачей. В результате предикат выведет на экран все нужные записи базы и завершится удачей.

Предикат filter_states_by_population принимает список государств и нижнее и верхнее значение населения, которые нас интересуют. Если список государство пуст — функция возвращает пустой список, в противном случае оно последовательно перебирает названия государств списка, выполняет их поиск в базе данных, сравнивает население с заданными границами и принимает решение добавить текущее государство к списку, полученному в результате рекурсивной обработки остальных государств или нет:

  filter_states_by_population([], _Lower, _Higher, []):-!.
filter_states_by_population([HeadState|TailStates], Lower, Higher, [HeadState|FilteredTail]):-
state(_Continent, HeadState, _Square, Population, _Capital),
Population >= Lower, Population <= Higher, !,
filter_states_by_population(TailStates, Lower, Higher, FilteredTail).
filter_states_by_population([_HeadState|TailStates], Lower, Higher, FilteredTail):-
filter_states_by_population(TailStates, Lower, Higher, FilteredTail).

Предикат print_sorted_by_populations выводит список государств, упорядоченный по населению. Функция принимает список всех государств, и список упорядоченный список со значениями населения (списки одинаковой длины). Если оба списка пусты — работа функции завершена. В противном случае из списка с населением выбирается первый элемент, выполняется поиск записи базы данных с таким значением населения, запись удаляется из базы (одна запись, т.к. возможно наличие нескольких государств с одинаковым населением), информация о удаленной записи выводится на экран, а остальные элементы списков обрабатываются рекурсивно. В результате записи из базы выбираются в порядке, соответствующем упорядоченным значениям населения, а удаление элемента из списка государств гарантирует корректную работу при наличии нескольких государств с одинаковым населением (в противном случае одна запись БД была бы выведена дважды, а вторая — ни разу):

  print_sorted_by_populations([], []):-!.
print_sorted_by_populations(States, [HeadPopulation|TailPopulations]):-
state(Continent, State, Square, HeadPopulation, Capital),
member(State, States), !, 
delete_single_element(States, State, StatesWithoutState),
print_state_information(Continent, State, Square, HeadPopulation, Capital), nl,
print_sorted_by_populations(StatesWithoutState, TailPopulations).

Два последних предиката могли бы быть записаны на диалектах с динамической типизацией (таких как SWI Prolog) более элегантно, т.к. предикат findall в них позволяет поместить в список кортежи любого вида:
findall((Continent, State, Square, HeadPopulation, Capital), state(Continent, State, Square, HeadPopulation, Capital), States).
А значит, можно отсортировать этот список и вывести результат на экран.

Наконец, меню для работы с нашей базой данных, построено аналогично тому как описано в теме «реализация меню на Prolog» — для идентификации правила, которое следует выполнить на текущем шаге используется числовой идентификатор. Каждое правило (кроме правила завершения работы) выполняет свою часть работы и выполняет menu(0), соответствующее выводу на экран меню и запроса следующего пункта. Последнее правило содержит анонимную переменную вместо числового идентификатора и срабатывает в случае если пользователь ввел некорректное значение:

  menu(0):-
write("1 - dump all database"), nl,
write("2 - select countries by continent"), nl,
write("3 - select countries sorted by populations"), nl, 
write("4 - select countries with population within the limits"), nl,
write("5 - insert country in database"), nl,
write("6 - remove entry from database"), nl,
write("7 - exit"), nl,
write(": "), readint(MenuPoint), 
menu(MenuPoint), !.
menu(1):-
state(Continent, State, Square, Population, Capital),
print_state_information(Continent, State, Square, Population, Capital), nl, fail;
!, menu(0).
menu(2):-
print_continents,
write("Select continent: "),  readln(SelectedContinent),
state(SelectedContinent, _State, _Square, _Population, _Capital), 
print_states_by_continent_information(SelectedContinent),
menu(0), !; write("such continent do not exist"), nl, menu(0), !.
menu(3):-
findall(Population, state(_Continent, _State, _Square, Population, _Capital), Populations),
findall(State, state(_Continent, State, _Square, _Population, _Capital), States),
qsort(Populations, SortedPopulations),
print_sorted_by_populations(States, SortedPopulations), 
menu(0), !.
menu(4):-
write("lower population limit: "), readReal(Lower),
write("higher population limit: "), readReal(Higher),
findall(State, state(_Continent, State, _Square, _Population, _Capital), States),
filter_states_by_population(States, Lower, Higher, FilteredStates),
print_states(FilteredStates), menu(0), !.
menu(5):-
print_continents,
write("Continent: "), readln(Continent), state(Continent, _State, _Square, _Population, _Capital),
write("State: "), readln(State), 
write("Square: "), readReal(Square),
write("Population: "), readReal(Population),
write("Capital: "), readln(Capital), 
assert(state(Continent, State, Square, Population, Capital)), !, menu(0);
write("Error: wrong data"), nl, !, menu(0).
menu(6):-
write("State: "), readln(State),
retract(state(_Continent, State, _Square, _Population, _Capital)), !, menu(0);
write("Error: wrong state"), nl, !, menu(0).
menu(7):-!.
menu(_):-
write("Error: bad menu point"), nl, !, menu(0).

В приведенном фрагменте используются типизированные операторы ввода, работающие в Turbo и Visual Prolog, в SWI Prolog нужно применять другие функции ввода.