Ответ в теме: Пример работы с базами данных в 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 нужно применять другие функции ввода.