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

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

В этой теме 2 ответа, 2 участника, последнее обновление  Васильев Владимир Сергеевич 1 год, 3 мес. назад.

  • Автор
    Сообщения
  • #2521

    questioner
    Участник

    Я прочитал, что во всех разновидностях языка Prolog имеется встроенная база данных, отличающаяся от SQL. Я посмотрел функции для работы с базами данных в SWI Prolog. Однако мне нужен более-менее приличный (учебный) пример использования таких баз, например — база данных государств (континент, государство, площадь, население, столица).

  • #2522

    За исключением небольших деталей, реализация для Visual Prolog не будет отличаться от других реализаций (например на SWI Prolog).

    Для начала в Visual Prolog нужно описать используемые в программе домены (типы данных), а для диалектов с динамической типизацией эту часть можно пропустить:

    domains
      d_continent = string
      d_state = string
      d_square = real
      d_population = real
      d_capital = string
      
      d_continents = d_continent*
      d_populations = d_population*
      d_states = d_state*

    Затем, нужно объявить тип записей базы данных — в SWI Prolog для этого используется ключевое слово dynamic (вы читали про нее в теме про функции для работы с БД), а в Visual Prolog — секция database:

    database -states_db
      nondeterm state(d_continent, d_state, d_square, d_population, d_capital)

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

      state ("Europe", "United Kingdom", 244.0, 57077.0, "London").
      state ("Europe", "Italy", 301.0, 57441.0, "Rome").
      state ("Asia", "Indonesia", 1905.0, 179140.0, "Jakarta").
      state ("Asia", "Iraq", 435.0, 17655.0, "Baghdad").
      state ("Asia", "Iran", 1648.0, 52522.0, "Tehran").
      state ("Asia", "Kuwait", 18.0, 2050.0, "Kuwait").
      state ("Asia", "Oman", 212.0, 1377.0, "Muscat").
      state ("Asia", "Saudi Arabia", 2150.0, 14430.0, "Al-Riyadh").
      state ("Africa", "Libya", 1760.0, 4232.0, "Tripoli").
      state ("North America", "USA", 9373.0, 248251.0, "Washington").
      state ("South America", "Argentina", 2767.0, 31930.0, "Buenos Aires").

    Пусть наша программа умеет: выводить всю информацию из базы; печатать информацию о странах заданного континента; выводить информацию о странах, упорядоченных по населению; добавлять и удалять записи базы данных. В диалектах prolog с динамической типизацией мы сразу можем приступить к написанию предикатов, но в SWI Prolog — сначала необходимо описать количество и типы их входных аргументов в секции predicates:

    predicates
      menu(integer)
      
      print_state_information(d_continent, d_state, d_square, d_population, d_capital)
      print_states_by_continent_information(d_continent)
      print_sorted_by_populations(d_states, d_populations)
      
      list_to_set(d_continents, d_continents, d_continents)
      nondeterm member(d_continent, d_continents)
      nondeterm member(d_state, d_states)
      
      qsort(d_populations, d_populations)
      divide(d_populations, d_population, d_populations, d_populations)
      append(d_populations, d_populations, d_populations)
      
      print_states(d_states)
      filter_states_by_population(d_states, d_population, d_population, d_states)
      
      print_continents

    Большая часть описанных предикатов уже описаны на блоге или являются стандартными: для сортировки записей нам пригодится функция qsort, которая использует функцию divide для разделения списка на элементы большие и меньшие заданного значения и встроенную функцию append для соединения списков. Кроме того, при сортировке нам потребуется удалять из списка один элемент списка с заданным значением, для этого напишем функцию delete_single_element, которую можно построить на базе стандартного select.

    Чтобы вывести все имеющиеся в базе данных континенты без повторений нам потребуется преобразовывать список во множество (удалять из него повторы) — для этого можно применить функцию list_to_set, которая использует встроенный предикат member для поиска элемента в списке.

  • #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 нужно применять другие функции ввода.

Для ответа в этой теме необходимо авторизоваться.