1 / 43

Логическое программирование

Логическое программирование. Факультет Прикладной математики и физики Кафедра Вычислительной математики и программирования Московский авиационный институт (государственный технический университет). Лекция 6. Рекурсивные структуры данных. Списки и деревья. Последовательности.

ronnie
Download Presentation

Логическое программирование

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Логическое программирование Факультет Прикладной математики и физики Кафедра Вычислительной математики и программирования Московский авиационный институт (государственный технический университет)

  2. Лекция 6 Рекурсивные структуры данных. Списки и деревья.

  3. Последовательности • Программирование замечательно способностью обрабатывать последовательности объектов • В императивном программировании – массив • Как быть в логическом программировании? • Нет переменных с возможностью модификации?

  4. Определение • Списком называется: • пустой список Ø • структурный терм вида Λ(t,l), где t — произвольный терм, l — список, Λ — имя структурного терма, используемого для построения списка. В списке такого вида t называется головой, а l — хвостом списка.

  5. Списки в Прологе • Этот принцип используется для представления списков в языках лог.программирования • Списочный терм обычно называют . (точка), а пустой список – []. L = .(1,.(2,.(3,[]))). L = [1,2,3]. ?- .(H,T) = .(1,.(2,.(3,[]))). H=1; T=[2,3]. ?- .(H,T) = [1,2,3]. H=1; T=[2,3]. ?- [H|T] = [1,2,3]. H=1; T=[2,3]. ?- [A,B|T] = [1,2].

  6. Представление списков деревьями • [[1,[2]],3,[4]] • Что будет головой и хвостом?

  7. Рекурсивная обработка рекурсивной структуры • Структура данных рекурсивна, потому что она определяется через саму себя • Соответственно, для такой структуры хорошо подходит рекурсивная обработка с pattern matching

  8. Пример • Определение суммы элементов списка • sum([1,2,3],X). sum([X],X). sum([X|T],S) :- sum(T,S1), S is S1+X. :- func sum(list(int))=int. :- mode sum(in) = out is det. sum([])=0. sum([X|T]) = X+sum(T). sum([X],X). sum([X|T],S) :- sum(T,S1), S = S1+X.

  9. Определение длины списка length(List,N) :- func length(list(T)) = int. :- mode length(in) = out is det. length([]) = 0. length([_|T]) = length(T)+1. length([],0). length([_|T],N) :- length(T,N1), N is N1+1. • В описании типа использован полиморфизм! • Предикат length не может использоваться для генерации списка переменных заданной длины • Упражнение: реализуйте предикат генерации списка заданной длины.

  10. Принадлежность элемента списку member(X,List) :- pred member(T,list(T)). :- mode member(in,in) is semidet. :- mode member(out,in) is nondet. member(X,[X|_]). member(X,[_|T]) :- member(X,T). member(X,[X|_]). member(X,[_|T]) :- member(X,T). • member используется не только для проверки, но и для генерации последовательности значений, для «вставки» значения на место в списке-шаблоне • member(X,[1,2,3]), write(X), fail. • L = [_,_,_], member(1,L), member(2,L), member(3,L).

  11. member для генерации значений member(X,[X|_]). member(X,[_|T]) :- member(X,T).

  12. Удаление элемента из списка remove(X,List,Result) :- pred remove(T,list(T),list(T)). :- mode remove(in,in,out) is nondet. :- mode remove(out,in,out) is nondet. :- mode remove(in,out,in) is multi. remove(X,[X|T],T). remove(X,[H|T],[H|R]) :- remove(X,T,R). remove(X,[X|T],T). remove(X,[H|T],[H|R]):- remove(X,T,R). • Удаляется лишь одно вхождение элемента • Может использоваться для генерации и проверки вхождения, например member(X,L) :- remove(X,L,_). • Упражнение: постройте предикат remove_all удаления всех вхождений элемента из списка • Упражнение: постройте предикат remove, который в случае, если элемента нет в списке, возвращает исходный список.

  13. remove для удаления ?-remove(X,[a,b,b,a],L). ?

  14. Конкатенация списков append(A,B,C) :- pred append(list(T),list(T),list(T)). :- mode append(in,in,out) is det. :- mode append(out,out,in) is multi. append([],L,L). append([X|T],L,[X|R]) :- append(T,L,R). append([],L,L). append([X|T],L,[X|R]):- append(T,L,R).

  15. Append для конкатенации append([],L,L). append([X|T],L,[X|R]):- append(T,L,R).

  16. Append для генерации пар append([],L,L). append([X|T],L,[X|R]):- append(T,L,R).

  17. Другие возможные варианты использования append • Для выделения первых/последних n элементов (rem_firstn/rem_lastn) • Для отделения/присоединения последнего элемента списка (last) • Для определения «соседних»элементов списка (next(A,B,L)) • Для выделения подсписка (sublist)

  18. Варианты использования append-решения rem_first(N,L,R) :- append(X,L,R), length(X,N). last(X,L) :- append(_,[X],L). next(A,B,L) :- append(_,[A,B|_],L). sublist(R,L) :- append(_,T,L), append(R,_,T).

  19. Построение перестановок permute(L,R) :- pred permute(list(T),list(T)). :- mode permute(in,in) is semidet. :- mode permute(in,out) is nondet. :- mode permute(out,in) is nondet. permute([],[]). permute(L,[X|T]) :- remove(X,L,R), permute(R,T). permute([],[]). permute(L,[X|T]) :- remove(X,L,R), permute(R,T). • Может использоваться для проверки перестановочности или генерации всех перестановок • При использовании для генерации первая переменная должна быть конкретизирована, иначе – процедурное зацикливание (проверьте!) • fact(N,F) :- gen_list(1,N,L), find_all(L1,permute(L,L1),R), length(R,F).

  20. Функции высших порядков • Функции sum, mult, max и т.д. являются частным случаем более общей логики применения некоторой функции ко всем элементам списка. • Это нельзя формализовать в логике первого порядка, но возможно, если допустить возможность передачи предиката в качестве параметра. • call позволяет вызвать предикат, переданный в качестве параметра

  21. Свертка списка: fold fold(List,Pred, Initial, Result) :- func fold(list(T),func(T,T)=T is det,T)=T is det. :- mode fold(in,in,in,out) is det. fold([],F,I) = I. fold([X|T],F,I) = F(fold(T,F,I),X). fold([],P,I,I). fold([X|T],P,I,R) :- fold(T,P,I,R1), call(P,R1,X,R). plus(X,Y,Z) :- Z is X+Y. mult(X,Y,Z) :- Z is X*Y. max(X,Y,Z) :- X>Y,Z=X. max(X,Y,Z) :- X=<Y,Z=Y. sum(L) = fold(L,(+),0). mult(L) = fold(L, lambda((X::in,Y::in)=Z::out is det, Z is X*Y),1)). sum(L,R) :- fold(L,plus,0,R). mult(L,R) :- fold(L,mult,1,R). max(L,R) :- fold(L,max,-1000,R).

  22. Отображение списка и фильтрация: map и filter map(List, Pred, Result) filter(List, Pred, Result) :- func map(list(T),func(T)=T is det) =list(T) is det. :- mode map(in,in)=out is det. map([],_) = []. map([X|T],F) = [F(X)|map(T,F)]. map([],_,[]). map([X|T],P,[Y|R]) :- call(P,X,Y), map(T,P,R). filter([],_,[]). filter([X|T],P,[X|R]) :- call(P,X), filter(T,P,R). filter([X|T],P,R) :- \+call(P,X), filter(T,P,R).

  23. Для разбора на семинаре • Сортировки списков • Вставка, выборка, пузырьковая, Хоара • Генерация списков • Одинаковых элементов • 1..N • Всех списков заданной длины с элементами из заданного множества • Выбор n-го элемента

  24. Подводим итоги

  25. Хвостовая рекурсия • В императивных языках многие операции со списками не требуют дополнительной памяти (длина списка, ...) • Во многих случаях (особенно при обработке списков) рекурсивные алгоритмы могут быть сведены к итерационным • Такая рекурсия называется хвостовой • Линейная • Рекурсивный вызов – в конце тела функции • т.е. вызов совершается сразу после выполнения тела функции • Не выделяется промежуточная память

  26. Нехвостовая рекурсия length([],0). length([_|T],N) :- length(T,N1), N is N1+1.

  27. Сведение length к хвостовой рекурсии За счет введения дополнительного аргумента (аккумулятора) удается проводить все вычисления (+1) до рекурсивного вызова, и рекурсия сводится в хвостовой. length(L,N) :-length(L,0,N). length([X|T],S,N) :- S1 is S+1, length(T,S1,N). length([],N,N).

  28. Реверсирование списка reverse([],[]). reverse([X|T],L) :- reverse(T,R), append(R,[X],L).

  29. Приводим к хвостовой рекурсии reverse(L,R) :- reverse(L,[],R). reverse([],R,R). reverse([X|T],L,R):- reverse(T,[X|L],R).

  30. Разностные списки • Декларативный список reverse(L,L1,L2) – список L2 получен из L1 добавлением в конец реверса списка L • Пара <L2,L1> называется разностным представлением списка, или разностным списком • Разностные списки удобно записывать в операторной нотации L2/L1 • Разностные списки часто бывают недоконкретизированными reverse(L,R) :- rev(L,R/[]). rev([],X/X). rev([X|T],R/S) :- rev(T,R/[X|S]).

  31. Разностный append • Рассмотрим разностные списки X/T и T/Y. • Очевидно, что dappend(X/T,T/Y,X/Y). ?-dappend([1,2|X]/[X],[3,4|Y]/Y,R/[]). • И это работает! • Append сводится только к унификации!

  32. Порядковое представление списков • В математике последовательность логично представлять функцией int -> T • Аналогичное представление в логике может задаваться перечислением: list(l1,1,a). list(l1,2,b). list(l1,3,c). L1 = [elem(1,a),elem(2,b),elem(3,c)]. • Некоторые операции оказываются эффективнее в порядковом представлении • Особенно полезно для представления матриц, в т.ч. разреженных

  33. Представление матриц • Удобно для разреженных матриц, при этом надо отдельно хранить размерность • Когда использовать то или иное представление? • Пример: транспонирование матрицы

  34. Деревья • Деревья являются для логического программирования «родной» структурой данных • Однако не деревья общего вида, поскольку арность функторов, описывающих дерево, должна быть фиксированной 1 2 3 4 5 6 7

  35. Сведение дерева общего вида к двоичному • Любое дерево общего вида может быть преобразовано к двоичному 1 1 2 2 3 4 5 3 5 6 7 6 4 7

  36. + 6 * 3 3 7 1 2 1 4 Двоичные деревья • Двоичным деревом называется: • пустое дерево Ø; • структурный терм вида T(t,l,r), где t — произвольный терм (узел дерева), l, r — двоичные деревья, называемые соответственно левым и правым поддеревьями.

  37. + 6 * 3 3 7 1 2 1 4 Представление двоичных деревьев T = +(*(1,2),3). T = t(6,t(3,t(1,nil,nil), t(4,nil,nil)), t(7,nil,nil). Значение узла задается одним из аргументов функутора Значение узла задается функтором (небольшое число узловых значений)

  38. Применение двоичных деревьев • Деревья поиска • Способ представления упорядоченных данных в памяти с эффективными алгоритмами добавления/удаления элемента и поиском • В неявном виде используются при определении структур данных типа ассоциативных таблиц, при индексировании и т.д. • Деревья выражений • Представление арифметических выражений с бинарными операторами

  39. 6 3 7 1 4 Дерево поиска • Все вершины левого поддереваменьше х • Все вершины правого поддерева больше х • Дерево поиска типа T, на котором определен полный порядок < - это двоичное дерево, для каждой вершины x которого

  40. Вставка в дерево поиска :- type tree(T) --> nil;tree(T,tree(T),tree(T)). :- func add(int,tree(int)) = tree(int). :- mode add(in,in) = out is det. add(X,nil) = tree(X,nil,nil). add(X,tree(Y,L,R)) = tree(Y,L1,R1):- X>Y --> L1=L, R1=add(X,R); X<Y --> L1=add(X,L), R1=R; L1=L, R1=R.

  41. Сортировка списка :- func to_tree(list(int)) = tree(int). :- mode to_tree(in) = out is det. to_tree(L) = T :- to_tree(L,nil,T).:- pred to_tree(list(int), tree(int), tree(int)). :- mode to_tree(in, in, out) is det. to_tree([],R,R). to_tree([X|L],T,R) :-to_tree(L,add(X,T),R). :- func from_tree(tree(int)) = list(int). :- mode from_tree(in) = out is det. from_tree(nil) = []. from_tree(tree(X,L,R)) = Z :- append(from_tree(L),[X|from_tree(R)],Z). :- func tsort(list(int)) = list(int). :- mode tsort(in) = out is det. tsort(L) = from_tree(to_tree(L)).

  42. Мораль • Рекурсивные структуры данных – часто используемый удобный механизм хранения данных в логическом программировании • Списки – гармоничная часть языка • Прозрачный синтаксис (специальный синтаксис конструкторов) • Во многих реализациях – встроенные библиотечные функции • Деревья (в особенности двоичные) для языков логического программирования являются базовой структурой • Применения деревьев: • Деревья выражений • Деревья поиска

More Related