
- •Глава 1. Введение в пролог
- •1. Декларативные и процедурные языки программирования
- •2. Пролог и логика предикатов. Внешние цели
- •3. Управление программой. Подцели. Механизм сопоставления
- •4. Внутренние подпрограммы унификации
- •Глава 2. Внутренние цели. Механизм возврата
- •1. Структура пролог-программы
- •2. Использование внутренних целей
- •3. Встроенный предикат fail
- •4. Сокращенные варианты внутренних запросов
- •5. Использование в запросах анонимных переменных
- •6. Механизм возврата
- •Глава 3. Типы данных и арифметика Turbo Prolog
- •1. Стандартные типы данных
- •2. Структуры, простые и составные
- •3. Структурные диаграммы
- •4. Использование в запросах анонимных переменных
- •5. Использование альтернативных доменов
- •6. Арифметика в Turbo Prolog
- •Глава 4. Предикат отсечения (!). Программирование альтернатив. Правила повтора
- •1. Повторения и возвраты
- •2. Отсечение (!)
- •3. Программирование альтернатив
- •4. Правило повтора
- •Глава 5. Методы организации рекурсии
- •1. Простая рекурсия
- •2. Метод обобщенного правила рекурсии
- •3. Граничное условие рекурсии. Нисходящая и восходящая рекурсии
- •4. Программа о подсчете числа точек
- •Глава 6. Списки
- •1. Основные понятия
- •2. Списки и турбо-пролог
- •3. Атрибуты списка
- •4. Внутреннее представление списков
- •5. Применение списков в программе
- •6. Метод разделения списка на голову и хвост
- •7. Поиск элемента в списке
- •8. Присоединение списка
- •9. Добавление и удаление элемента
- •10. Подсписок
- •11. Перестановки списка
- •Глава 7. Сортировка списков
- •1. Разделение списка на два
- •2. Сортировка списков методом вставки
- •3. Быстрая сортировка
- •4. Быстрая сортировка_1
- •5. Компоновка данных в список
- •Глава 8. Программирование алгоритмов с возвратом. Представление графов в turbo prolog
- •1. Задача о весах
- •2. Представление графов в turbo prolog
- •3. Поиск пути на неориентированном графе
- •4. Поиск гамильтоновых циклов
- •5. Поиск пути минимальной стоимости
- •Глава 9. Динамическая база данных
- •1. Турбо-пролог и реляционные базы данных
- •2. Описание предикатов динамических бд
- •3. Встроенные предикаты asserta, assertz, retract, retractall, save, consult
- •4. Создание динамической базы данных
- •5. Обсуждение проекта базы данных
- •6. Создание базы данных
- •7. Написание программных модулей
- •Глава 10. Глобальные переменные в turbo prolog
- •1. Модификация базы данных
- •2. Накопление результатов с помощью вынуждаемого возврата
- •3. Подсчет членов парторганизации
- •4. Поиск пути минимальной стоимости от a до z
- •Библиографический список
- •Оглавление
5. Компоновка данных в список
Иногда, при программировании определенных задач, возникает необходимость собрать данные из базы данных в список для последующей их обработки. Турбо-Пролог содержит встроенный предикат, позволяющий справиться с этой задачей без каких-бы то ни было хлопот. Таким предикатом является предикат findall. Требуемый список представляется означенной переменной, являющейся одним из объектов предиката.
Предописание встроенного предиката findall выглядит следующим образом:
fidall(Variable_name,Predicate_expression,List_name)
Variable_name обозначает здесь объект входного предиката Predicate_expression, а List_name является именем переменной выходного списка. Переменная должна относиться к домену списков, объявленному в разделе domains.
Для пояснения только что сказанного, рассмотрим предикат male из программы 2.1 «РОДСТВЕННИКИ» главы 2.
Соберем в список всех мужчин рассматриваемой семьи. Вначале в разделе domains опишем соответствующий список:
domains
name = symbol
list = symbol*
и составим этот список Mlist с помощью предиката findall:
goal
findall(X, male(X), Mlist), write(Mlist).
Предикат findall может работать не только с фактами, но и с правилами. Соберем в список Slist всех сестер:
goal
findall(X, sister(X,_), Slist), write(Slist).
или всех сестер и братьев beth:
goal
findall(X,sister(beth,X),Blist), write(Blist).
Упражнение 7.2.
Собирите в список всех родственников бэт и удалите повторяющиеся элементы. Для удаления из списка кратностей запрограммируйте процедуру delcrat следующему алгоритму:
1. Пустой список — список без кратностей.
2. Если голова списка H принадлежит хвосту списка T (member(H,T)), то вызываем delcrat для хвоста списка.
3. Если голова списка H не принадлежит хвосту списка T, то H является головой целевого списка, а хвост целевого списка T’ получается после удаления кратностей из хвоста T: delcrat(T,T’).
Глава 8. Программирование алгоритмов с возвратом. Представление графов в turbo prolog
1. Задача о весах
Рассмотрим задачу о наборе заданного веса с помощью имеющегося разновеса всеми возможными способами. Отдельно рассмотрим два случая:
1) отсутствие в разновесе кратных гирь, то есть гирь с одинаковым весом;
2) наличие в разновесе кратных гирь.
РАЗЛИЧНЫМИ будем считать наборы с точностью до перестановки гирь. Для этого генерировать наборы будем в ЛЕКСИКОГРАФИЧЕСКОМ порядке (вернее, в порядке обратном лексикографическому), т. е. вначале найдем все наборы, содержащие самую тяжелую гирю, затем наборы, содержащие вторую по тяжести гирю и.т.д. Внутри наборов гири будут упорядочены таким же образом в порядке убывания.
Алгоритм с возвратом будет генерировать наборы следующим образом:
если очередная гиря G меньше текущего веса VES, который нужно набрать, то
— переносим гирю G из разновеса в решение;
— пытаемся набрать уменьшенный вес VES1 (VES-G) гирями весом не более G.
Если для VES1 в разновесе нет подходящей гири или VES1=0 (нашли решение), то происходит ВОЗВРАТ:
— последняя добавленная гиря G из решения возвращается в разновес;
— текущим весом становится VES (VES1+G), а текущей гирей — гиря весом G-1. Если такой гири в разновесе нет, то берем гирю G-2 и т.д.
Различие в работе алгоритма для случая кратных и однократных гирь заключается в следующем:
1) в случае кратных гирь после добавления G к решению уменьшенный вес набирается гирями веса G,
2) в случае однократных гирь уменьшенный вес набирается гирями веса <G (начиная с G-1).
При возврате (после генерации всех наборов веса VES, начинающихся с гири G) и в первом, и во втором случае дальнейшие разложения веса VES строятся, начиная с гири G-1. Благодаря этому, в случае кратных гирь мы избегаем повторяющихся наборов.
Для разновеса [5,4,3,2,1,1,1] алгоритм с возвратом построит следующие решения:
[5] [4,1] [3,2] [3,1,1 ] [2,1,1,1]
Дерево поиска, которое строит алгоритм с возвратом, изображено на рис. 8.1.
рис. 8.1
Запрограммируем случай однократных гирь (программа 8.1). Разновес и решения будем хранить в виде списков, упорядоченных в порядке убывания.
Идея поиска:
1. Нулевому весу соответствует решение — пустой список.
2. При наборе непустого веса берем первую гирю из текущего разновеса. Хвост разновеса становится текущим разновесом, а уменьшенный вес — текущим весом.
3. При возврате (или закончились гири, или первая гиря разновеса оказалась недопустимой) первую гирю разновеса убираем и набираем текущий вес с помощью хвоста разновеса.
Таким образом, для непустого веса имеются два неисключающих друг друга правила:
или берем первую гирю, или пропускаем ее.
Благодаря этим трем правилам, мы получим всевозможные наборы веса без повторений.
/* Программа 8.1 «ВЕСЫ». Назначение: */
/* демонстрация алгоритма с возвратом */
domains
list=integer*
predicates
solve1(list,integer,list) % однократные гири
goal
write("Вес ",5," можно набрать: "),nl,
solve1([5,4,3,2,1], 5, S),
write(S),nl,fail.
clauses
/*** Правила однократной процедуры solve1 ***/
solve1(_,0,[]):-!.
solve1([H|T],Ves,[H|T1]):-
H <= Ves,
Ves1=Ves-H,
solve1(T,Ves1,T1).
solve1([_|T],Ves,S):-
solve1(T,Ves,S).
/* Конец программы */
Если убрать ! из правила для нулевого веса, в последнее правило нужно добавить условие Ves>0. Иначе после печати найденного решения произойдет возврат на последнее правило solve1 и это решение будет повторено столько раз, сколько гирь в хвосте T.
Рассмотрим случай кратных гирь.
Согласно алгоритму, при возврате должен происходить сдвиг с гири G на гирю G-1(или меньшую), иначе мы будем получать повторяющиеся решения (например, для трех гирь 1 решение [4,1] будет повторено три раза). В первом случае это происходило автоматически, когда при возврате пропускалась голова разновеса. Если в разновесе будут стоять подряд повторяющиеся гири, то, пропустив голову, мы можем попасть на гирю такого же веса.
Будем хранить разновес в виде двух списков:
в первом — различные веса, имеющиеся в разновесе,
во втором — их кратности.
Так, разновес [5,4,3,2,2,1,1,1] будет представлен в виде двух списков: [5,4,3,2,1] и [1,1,1,2,3].
Правило выбора первой гири преобразуется в правило для однократной гири и правило для кратной гири.
/* Программа 8.2 «КРАТНЫЕ ВЕСЫ». Назначение: */
/* демонстрация алгоритма с возвратом */
domains
list=integer*
predicates
solve(list,list,integer,list) % кратные гири
goal
write("Вес ",5," можно набрать: "),nl,
solve([5,4,3,2,1],[2,1,1,2,3], 5, S),
write(S),nl,fail.
% 1-й список solve содержит веса гирь.
% 2-й список solve содержит кратности гирь.
% 3-й список solve содержит разложение веса.
clauses
/*** Правила кратной процедуры solve ***/
solve(_,_,0,[]):-!.
% 1-я альтернатива: набрали нужный вес.
% Переход на нижние правила solve запрещен.
% Возврат на цель solve верхнего уровня.
% ! можно заменить условием Ves>0 в последнем правиле.
solve([H|T],[1|CT],Ves,[H|T1]):-
H <= Ves,
Ves1=Ves-H,
solve(T,CT,Ves1,T1).
% 1-я альтернатива: однократная гиря H.
solve([H|T],[N|CT],Ves,[H|T1]):-
N>1,
N1=N-1,
H <= Ves,
Ves1=Ves-H,
solve([H|T],[N1|CT],Ves1,T1).
% 2-я альтернатива: N-кратная гиря H.
% Набор Ves1 начинается N-1-кратной H.
solve([_|T],[_|T1],Ves,S):-
solve(T,T1,Ves,S).
% Возврат для 1 и 2 альтернатив:
% H пропускается, берется следующая гиря.
/* Конец программы */
Из приведенных примеров видно, что ПРОГРАММИСТУ НЕ НУЖНО СЛЕДИТЬ ЗА ВОССТАНОВЛЕНИЕМ ИСХОДНОЙ СИТУАЦИИ ПРИ ВОЗВРАТЕ:
удаление гири из решения и возвращение ее в разновес, восстановление текущего веса — это при возврате АВТОМАТИЧЕСКИ ДЕЛАЕТ САМА СИСТЕМА. Для того чтобы система могла осуществить возврат, правило solve было сделано НЕДЕТЕРМИНИРОВАННЫМ (или берем первую гирю, или пропускаем ее).