
- •1.Введение в функциональное и логическое программирование
- •1.1. Основные классы вычислительных моделей
- •1.1.1 Процедурная вычислительная модель
- •1.1.2 Функциональная вычислительная модель
- •1.1.3. Логическая вычислительная модель
- •1.1.4. Объектно-ориентированная вычислительная модель (ооп)
- •1.2.Метод оценки (способ получения результатов)
- •1.3. Обмен информацией в процессе оценки
- •1.4.Понятие искусственного интеллекта
- •1.5.Символьные языки программирования
- •1.6. Основные направления в искусственном интеллекте
- •Для работы в сфере ии надо иметь:
- •1) Языки ии, обеспечивающие простоту модификации
- •3) Важна и структура самой машины, позволяющая с
- •2.Основы логического программирования и язык Пролог.
- •2.1.Основные понятия
- •2.2.Определение отношений на основе фактов и правил
- •2.3 Пример программы на языке Пролог
- •2.4.Использование рекурсии
- •2.5.Декларативная и процедурная трактовка программы
- •2.6. Cтруктура программы
- •2.7.Особенности лп:
- •2.8 Использование предиката not и
- •3.Синтаксис Пролога
- •3.1. Объекты данных
- •3.2. Составные объекты и альтернативные домены. В утверждениях объекты представляют собой данные.
- •3.2.1. Составная структура
- •Листинг3.1. Использование доменной структуры с именем personal_library
- •3.2.2.Доменная структурная диаграмма программы “Библиотека” (дсд)
- •3.2.3.Предикатная структурная диаграмма программы “Библиотека” (псд)
- •3.2.4. Альтернативные домены
- •Выводы:
- •Рассмотрение работы с составными объектами и альтернативными доменами закончено. Посмотрим, как именно строится логический вывод, реализованный на эвм?
- •3.3. Сопоставление структур(matching)
- •3.4.Унификация и подстановки (Unify).
- •3.5.Основные правила поиска с возвратом:
- •Листинг 3.4.Унификация и поиск с возвратом
- •4.Принцип резолюции
- •Метод резолюции в исчислении предикатов – это пра-
- •4.1.Логическое следствие
- •4.2. Логический вывод
- •4.3 Преимущества и недостатки метода резолюции
- •4.4. Пример применения метода резолюций.
- •5. Управление поиском решений
- •5.1.Метод отката после неудачи – опн
- •5.2 Метод отсечения и отката – оо
- •5.2.1.Влияние предиката cut на составную цель
- •5.2.3. Использование зеленых и красных отсечений
- •5.2.4.Использование предиката not как средства управления
- •5.3.Метод повтора, определяемый пользователем (мп)
- •5.4.Методы организации рекурсии
- •Листинг 5.9. Бесконечная рекурсия (хвостовая рекурсия)
- •Пример программы, которая циклически считывает символ, введенный пользователем. Если символ не равен #, то он выводится на экран, иначе процесс заканчивается.
- •Листинг 5.14. Пример рекурсии для генерации ряда чисел в порядке возрастания
- •Определение
- •Список помогает сделать программу компактной, эффектив-
- •Список – это рекурсивный составной объект, поэтому
- •6.1.Операции над структурами данных типа список.
- •6.2.Предикат findall
- •6.3. Операции со структурами данных.
6.1.Операции над структурами данных типа список.
1 Принадлежность списку
2 Определение длины списка.
3 Вывод списка на печать
4 Модификация списка
5 Деление списка(X,L,L1,L2)
6 Удаление элемента из списка (X,L1,L2)
7 Объединение списков(L1,L2,L3)
8 Cортировка списков
Листинг6.1.1. Поиск нужного элемента(принадлежность списку)
domains
list=integer*
predicates
member(integer,list)
clauses
(1) member(Head,[Head|_]).
(2) member(Head,[_|Tail]):-
member(Head,Tail).
Алгоритм: (декларативная точка зрения)
Элемент принадлежит списку, если
(1) первый элемент списка и есть искомый элемент
(2) искомый элемент входит в состав хвоста списка
С процедурной точки зрения предложения программы 1 и 2 можно трактовать так: чтобы найти принадлежность элемента списку, надо проверить, не совпадает ли элемент с головой списка и если нет, то попытаться найти этот элемент в хвосте списка.
Цели
? member(3,[5,6,3,8,9]) –просим Пролог выяснить верно ли это утверждение.
(ответ: Yes или No в зависимости от того, входит или нет элемент с состав списка)
? member(X,[6,8,9])- вывод всего списка
И процедурная и декларативная точки зрения соотносятся с целями 1 и 2.
(ответ:
X=6
X=8
X=9
)
Модифицируем первое предложение member(Head,[Head|__]):-!.
Как изменится ответ, если поставим цель ?member(X,[6,8,9])
(ответ: X=6- только одно решение) Отсечение не позволит нам получить оставшиеся элементы списка, следовательно, это можно использовать только для получения одного конкретного значения –первого элемента списка.
Проверим, имеет ли значение порядок написания двух предложений для предиката member()? Поменяем местами эти предложения. Поставим цель member(X,[6,8,9]).Ответ будет напечатан в обратном порядке:
(ответ:
X=9
X=8
X=6
)
Убедились, что с процедурной точки зрения порядок предложений в программе важен. Изменение порядка правил в процедуре приведет к перестановке ветвей в дереве поиска цели, использующей данную процедуру. Само дерево поиска не изменится, но обход дерева будет производиться в ином порядке, а значит и процесс унификации пойдет иначе.
Замечание: Порядок целей более существенен, чем порядок предложений. Порядок целей имеет решающее значение при определении последовательности действий в программе. Изменение порядка целей приведет к изменению дерева поиска.
Листинг6.1.2. Определение длины списка
domains
list=integer*
predicates
length_of(list,integer)
clauses
length_of([],0).
length_of([_|T],L):-
length_of(T,TailLength),
L=TailLength+1.
Терминальный случай- длина пустого списка =0.
Нетерминальный случай-правило рекурсии – длина любого
другого списка равна 1+длина его хвоста.
Goal: clearwindow, length_of([5,7,1,4],L).
При нисходящем построении рекурсии решение откладывается до тех пор, пока не станет известен последний член списка
Length_of([5,7,1,4]) L1=L2+1=4
length_of([7,1,4],L2) L2=L3+1=3
length_of([1,4],L3) L3=L4+1=2
length_of([4],L4) L4=0+1=1
length_of([],0) 0
Ответ: L=4 1 Solution
Замечание
Отличия от процедурных языков:
1. Вместо изменения значения переменной с помощью присваивания, повторно вызываем функцию с измененными значениями ее параметров
2. Вместо использования безусловного перехода и итерации применяем условное выражение и рекурсию.
3.Плохо то, что для организации рекурсии требуется большой стек, а механизм связывания, используемый для адресации позиции текущей переменной в этом стеке, может замедлить исполнение для фон-неймановских машин (сейчас, правда, это не так важно).
Листинг 6.1.3.Модификация списка (программа работает со списком и добавляет 1 к каждому элементу списка)
domains
list=integer*
predicates
add1(list,list)
clauses
add1([],[]).
add1([Head|Tail],[Head1|Tail1]):-
Head1=Head+1,
add1(Tail,Tail1).
goal
add1([10,21,3,74],NewList),
write(NewList).
(ответ-[11,22,4,75])
стек: 75 из стека идет обратная раскрутка
74 [74],[75]
4 [3,74],[4,75]
3 [21,3,75],[22,4,75]
22 [10,21,3,74],[11,22,4,75] и печать
21
11
10
Ответ: NewList=[11,22,4,75]
Объединение списов (конкатенация)
Особенность Пролога заключается в том, что задавая утверждения для предиката с логической точки зрения, эти утверждения могут быть выполнены с процедурной точки зрения.
То есть здесь присутствует двойственность, и эту двойственность надо увидеть и учесть. Воспользуемся рекурсией с процедурной точки зрения для объединения списков.
Определим предикат append с тремя аргументами
append(List1,List2,List3)
List1, List2 – исходные списки,
List3- список, получающийся при их объединении.
Например, append([1,2],[3,4],[1,2,3,4])-true
append([1,2],[3,4],[1,2,1,3,4])-false
Определим отношение append,как содержащее два случая в зависимости от вида List1:
1. Если List1-пуст, то второй и третий аргументы представляют собой один и тот же список (назовем его List2):
append([],List2,List2).
2. Если List1 –не пуст, то он имеет голову и хвост [H|L1], и можно объединить List1 и List2 для формирования List3,сделав голову List1 головой List3.
Хвост List3 состоит из объединения остатка List1 и всего List2:
append(H|L1],List2,[H|L3]:-
append(L1,List2,L3).
Листинг 6.1.4. Программа объединения списков
domains
list=integer*
predicates
append(list,list,list)
clauses
append([],List,List).
append([H|L1],List2,[H|L3]):-
append(L1,List2,L3).
Поставьте цели:
1.?append([1,2],[3,4,5],L).
Ответ:L=[1,2,3,4,5] 1Solution
2.?append([1,2],[3],L),append(L,L,LL).
Ответ: L=[1,2,3]
LL=[1,2,3,1,2,3] 1Solution
3.?append([1,2],L,[1,2,5,6]) –разность между списками
Ответ:L=[5,6] 1Solution
4.? append(L,[5,6],[1,2,5,6] –разность между списками
Ответ L=[1,2] 1Solution
Замечание для 3 и 4:
Первые два аргумента отношения append не симметричны, поэтому имеется два различных варианта нахождения разности между двумя списками.
Рассматривая append с декларативной точки зрения мы определяем отношение между тремя списками. Это отношение сохранится даже если List1 и List3 известны, а List2 –нет. Оно также справедливо, если известен только List3.
Таким образом, предикат append определяет отношение между входным набором и выходным набором таким образом, что отношение применимо в обоих направлениях Задавая отношения вы можете спросить систему
–Какой выход соответствует данному входу?
-Какой вход соответствует данному выходу?
Состояние аргументов при вызове предиката называется потоком параметров. Аргумент, который присваивается или назначается в момент вызова, называется входным аргументом и обозначается буквой”i”; а свободный аргумент – это выходной аргумент, и он обозначается буквой “o”.
У предиката append есть возможность работать с разными потоками параметров в зависимости от того, какие исходные данные ему зададут. Если утверждение Пролога может быть использовано с различными потоками параметров, оно называется обратимым утверждением. Обратимые утверждения имеют дополнительные преимущества, и их создание добавляет “мощности” предикатам.
Использование программы append в обратном направлении:
Goal: (L1,L2,[1,2,3,6]).
Ответ:
L1=[] L2=[1,2,3,6]
L1=[1] L2=[2,3,6]
L1=[1,2] L2=[3,6]
L1=[1,2,3] L2=[6]
L1=[1,2,3,6] L2=[] 5 Solutions
Если бы объекты были не числа, а дни недели,то:
domains
list=string*
Goal: append(Before,[friday|After],[monday,tuesday,wednesday,thursday,friday,saturday,sunday]).
Ответ:
Before=(“monday”,”tuesday”,’wednesday’,”thursday”]
After=[“saturday”,”sunday”]
1 solution
goal: append(L1,[b|_],[a,b,c])
ответ
L1=[“a”] 1Solution
goal: L1=[a,b,c,d,f,f,m],append(L2,[f|_],L1)
ответ:
L1=[a,b,c,d,f,f,m] L2=[a,b,c,d]
L1=[a,b,c,d,f,f,m] L2=[a,b,c,d,f]
2 solutions
(удаление всего, что стоит следом за f)
Замечание: логические программы не содержат алгоритма, для решения логической задачи достаточно точного логического описания. Так как в логической программе последовательность и способ выполнения программы не фиксируется, как при описании алгоритма, программы в принципе могут работать в обоих направлениях. Прологовская программа может на основе исходных данных вычислить результат, но с тем же успехом без дополнительного программирования на основе результата –вычислить исходные данные.
(В традиционных языках программирования данные обрабатываются в порядке, задаваемом описанием алгоритма. Это определяет несимметричность функции, то есть вычислить значения аргументов, исходя из значения функции нельзя!)
Ранее мы ввели предикат member.Такие предикаты как member, sublist, prefix могут быть определены в терминах предиката append, если последний использовать не для объединения, а для разделения(расщепления) списков.
Попробуем определить отношение принадлежности, используя конкатенацию.
member(X,L):-
append(L1,[X|L2],L).
X принадлежит L, если список L можно разбить на два списка L1 и L2 так, чтобы элемент X являлся головой списка L2. Поставим цель ? member(2,[1,3,2,5,7]). Предикат member будет просматривать список L элемент за элементом до тех пор, пока не встретит 2, или пока не кончится список. Ответ: yes –no
Листинг 6.1.5.Определение принадлежности через объединение
domains
list=integer*
predicates
member(integer,list)
append(list,list,list)
clauses
member(X,L):-
append(L1,[X|L2],L).
append([],L,L).
append([X|L1],L2,[X|L3]):-
append(L1,L2,L3).
Попытайтесь самостоятельно использовать предикат append для нахождения префиксов, суффиксов, подсписков.
Листинг 6.1.6.Деление списка
domains
middle=integer
list=integer*
predicates
split(middle,list,list,list)
clauses
split(_,[],[],[]).
Split(Middle,[Head|Tail],[Head|L1],L2):-
Head<=Middle,
Split(Middle,Tail,L1,L2).
Split(Middle,[Head|Tail],L1,[Head|L2]):-
Head>Middle,
Split(Middle,Tail,L1,L2).
Middle-компаратор-элемент данных
L-исходный список
L1,L2 – подсписки, получающиеся в результате деления
списка L.
Алгоритм
Очередной элемент извлекается из списка при помощи метода разделения списка на голову и хвост, а потом голова сравнивается с компаратором Middle.Если значение этого элемента <= Middle,то элемент помещается в список L1,иначе - в список L2. Элементы и компаратор должны быть одного типа.
Goal: split(12,[96,2,8],L1,L2)
Ответ:L1=[2,8]
L2=[96]
Листинг 6.1.7. Удаление элемента из списка
domains
list=integer*
predicates
delete(integer,list,list)
clauses
(1) delete(X,[X|Tail],Tail).
(2) delete(X,[Y|Tail],[Y|Tail1]):-
delete(X,Tail,Tail1).
(1) Если X – голова списка, то результат –хвост этого списка.
(2) Если X – в хвосте, то его надо удалить оттуда.
Отношение delete по своей природе – недетерминировано и обратимо.
Цели:
? delete(5,[1,8,5,7,5],L)
Ответ L=[1,8,7,5] -удаление первой 5
L=[1,8,5,7] –удаление последней 5
Если в списке содержится несколько вхождений элемента X, то delete может исключить их все при помощи возврата и дать нам различные варианты удалений, каждый раз удаляя лишь одно вхождение X.Остальные вхождения остаются в неприкосновенности (недетерминированность). При попытке исключить элемент, отсутствующий в списке, отношение delete потерпит неудачу
? delete(5,L,[1,8,7]) – используем для вставки элемента (обратимость)
Ответ L=[5,1,8,7]
L=[1,5,8,7]
L=[1,8,5,7]
L=[1,8,7,5] 4Solution
Отношение delete – обратимое, его можно использовать в обратном направлении для того, чтобы добавить элементы в список, вставляя их в произвольные места списка. Для этого зададим вопрос:”Каким должен быть список L,чтобы после удаления из него элемента “5” получить список [1,8,7] ”?
Отношение delete можно использовать для проверки на принадлежность списку.
Идея:X принадлежит списку, если этот X можно из списка удалить:
member(X,List):-
delete(X,List,_).
Delete можно использовать для построения всех возможных перестановок списка .Для этого надо включить отношение change с двумя аргументами
Листинг 6.1.8 Перестанова элементов списка с помощью удаления
domains
list=integer*
predicates
delete(integer,list,list)
change(list,list)
clauses
delete(X,[X|Tail],Tail).
delete(X,[Y|Tail],[Y|Tail1]):-
delete(X,Tail,Tail1).
change([],[]).
change(L,[X|P]):-
delete(X,L,L1),
change(L1,P).
? (change([3,5,6],L)---L=[3,5,6] L=[3,6,5] L=[5,3,6] L=[5,6,3]
L=[6,3,5] L=6,5,3] 6 Solutions
Но надо быть осторожным ( при change(L,[3,5,6])—произойдет зацикливание)