Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
учебное_пособие_флп.doc
Скачиваний:
1
Добавлен:
01.05.2025
Размер:
675.84 Кб
Скачать

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])—произойдет зацикливание)