В императивных языках, как правило, основной структурой данных являются массивы. В Прологе так же, как и в Лиспе, основным составным типом данных является
Дадим сначала неформальное определение списка.
Будем называть списком упорядоченную последовательность элементов произвольной длины.
[monday, tuesday, wednesday, thursday, friday, saturday, sunday] —
["понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье"] —
[1, 2, 3, 4, 5, 6, 7] —
['п', 'в', 'с', 'ч', 'п', 'с', 'в'] —
[] — пустой
В разделе описания доменов списки описываются следующим образом:
DOMAINS <имя спискового домена>=<имя домена элементов списка>*
Звездочка после имени домена указывает на то, что мы описываем
Например:
listI = integer* /* список, элементы которого —
целые числа */
listR = real* /* список, состоящий из вещественных
чисел */
listC = char* /* список символов */
lists = string* /* список, состоящий из строк */
listL = listI* /* список, элементами которого являются
списки целых чисел */
Последнему примеру будут соответствовать списки вида:
[[1,3,7],[],[5,2,94],[–5,13]]
В классическом Прологе [monday, 1, "понедельник"]
В Турбо Прологе, в связи со
Например, следующее описание:
DOMAINS element = i(integer); c(char); s(string) listE = element*
позволит иметь дело со списками вида
[i(–15), s("Мама"),c('A'),s("мыла"),c('+'),s("раму"),
i(48),c('!')]
Дадим рекурсивное определение списка.
[ ] ) является списком;[H|T] является списком, если H — первый T — Принято называть H T —
Фактически операция "|" позволяет разделить
Данное определение позволяет организовывать рекурсивную обработку списков, разделяя непустой
Например, в списке [1, 2, 3] элемент 1 является головой, а [2, 3] — хвостом, т.е. [1, 2, 3] = [1|[2, 3]].
Заметим, что хвост этого списка [2, 3], в свою очередь, может быть представлен в виде головы 2 и хвоста [3], а [3] можно рассматривать в виде головы 3 и хвоста []. Пустой
В итоге получаем, что [1, 2, 3] эквивалентен списку [1|[2, 3]], который, в свою очередь, эквивалентен списку [1|[2|[3]]]. Последний сопоставим со списком [1|[2|[3|[ ]]]].
В этом же списке можно выделить два первых элемента и хвост из третьего элемента [1,2|[3]]. И, наконец, возможен вариант разбиения на голову из трех первых элементов и пустой хвост: [1, 2, 3|[]].
Чтобы организовать обработку списка, в соответствии с приведенным выше рекурсивным определением, нам достаточно задать предложение (правило или факт, определяющее, что нужно делать с пустым списком), которое будет базисом рекурсии, а также рекурсивное правило, устанавливающее порядок перехода от обработки всего непустого списка к обработке его хвоста. Иногда базис рекурсии записывается не для пустого, а для одно- или двухэлементного списка.
В качестве резюме к нашим рассуждениям запишем еще раз определение списка в нотации Бэкуса–Науэра:
Список ::= [ ]|[Элемент <,Элемент>*]|[Голова|Хвост] Голова ::= Элемент <,Элемент>* Хвост ::= Список
Словесно это можно записать так:
Рассмотрим обработку списков.
Пример. Создадим предикат, позволяющий вычислить длину списка, т.е. количество элементов в списке.
Для решения этой задачи воспользуемся очевидным фактом, что в пустом списке элементов нет, а количество элементов непустого списка, представленного в виде объединения первого элемента и хвоста, равно количеству элементов хвоста, увеличенному на единицу. Запишем эту идею:
length([], 0). /* в пустом списке элементов нет */
length([_|T], L) :–
length(T, L_T), /* L_T — количество
элементов в хвосте */
L = L_T + 1. /* L — количество элементов
исходного списка */
Обратите внимание, что при переходе от всего списка к его хвосту нам не важно, чему равен первый
Разберем на примере, как это будет работать. Пусть нас интересует количество элементов в списке [1,2,3]. Запишем соответствующий вопрос Пролог-системе:
length([1,2,3],X).
Система попытается вначале сопоставить нашу цель с первым предложением length([], 0), однако ей это не удается сделать, потому что первый аргумент цели является непустым списком. Система переходит ко второму предложению процедуры. Сопоставление с заголовком правила проходит успешно, переменная X связывается с переменной L, [1,2,3] будет сопоставлен со списком [_|T], переменная T будет конкретизирована значением [2,3]. Теперь система переходит к попытке достижения подцели length(T,L_T). Как и в предыдущем случае, первое предложение с подцелью не сопоставляется, так как T не пустой. При сопоставлении заголовка правила с подцелью хвост T конкретизируется одноэлементным списком [3]. На следующем шаге рекурсии переменная T означена пустым списком (хвост одноэлементного списка). И, значит, наша подцель выглядит следующим образом: length([], L_T). Эта цель сопоставляется с фактом, переменная L_T становится равной нулю. Раскручивается обратный ход рекурсии: переменная L_T увеличивается на единицу, результат попадает в переменную L. Получаем, что длина списка [3] равна единице. На следующем обратном шаге происходит еще одно добавление единицы, после чего длина списка [2,3] конкретизируется двойкой. И, наконец, на последнем возвратном шаге получаем означивание переменной L числом 3 (количеством элементов в списке [1,2,3] ).
Пример. Создадим предикат, позволяющий проверить принадлежность элемента списку. Предикат будет иметь два аргумента: первый — искомое значение, второй —
Построим данный предикат, опираясь на тот факт, что объект принадлежит списку, если он либо является первым
member(X,[X|_]). /* X — первый элемент списка */
member(X,[_|T]) :–
member(X,T). /* X принадлежит хвосту T*/
Заметим, что в первом случае (когда первый X принадлежит хвосту, нам не важно, какой элемент первый.
Отметим, что описанный предикат можно использовать двояко: во-первых, конечно, для того, для чего мы его и создавали, т.е. для проверки, имеется ли в списке конкретное значение. Мы можем, например, поинтересоваться, принадлежит ли двойка списку [1, 2, 3]:
member(2, [1, 2, 3]).
Получим, естественно, ответ: "Yes".
Подобным образом можно спросить, является ли число 4 [1, 2, 3]:
member(4, [1, 2, 3]).
Ответом, конечно, будет "No".
Второй способ использования данного предиката — это получение по списку его элементов. Для этого нужно в качестве первого аргумента предиката указать свободную переменную. Например:
member(X, [1, 2, 3]).
В качестве результата получим
X=1 X=2 X=3
Третий способ позволит получить по элементу варианты списков, которые могут его содержать. Теперь свободную переменную запишем вторым аргументом предиката, а первым — конкретное значение. Например,
member(1, X).
Вначале Пролог-система выдаст предупреждение о том, что переменная X не связана в первом предложении ( "708 WARNING: The variable is not bound in this clause. (F10=ok, Esc=abort)" ).
У нас есть два способа отреагировать на это предупреждение: нажать кнопку Esc, чтобы отказаться от генерации списков, содержащих единицу в качестве элемента; нажать F10 для того, чтобы продолжить выполнение цели. Во втором случае Пролог-система начнет выдавать варианты списков, содержащих единицу:
X=[1|_] /* единица — первый элемент списка */ X=[_,1|_] /* единица — второй элемент списка */ X=[_,_,1|_] /* единица — третий элемент списка */ и т.д.
Этот процесс будет продолжаться до тех пор, пока не будет нажата комбинация клавиш Ctrl+Break.
Если данный предикат планируется использовать только первым способом, то можно ускорить его работу, устранив поиск элемента в
Первый способ. Добавим в правило проверку на несовпадение первого
member2(X,[X|_]).
member2(X,[Y|T]):–
X<>Y, member2(X,T).
Заметим, что эту модификацию предиката member нельзя использовать для получения всех X будет сравниваться с неозначенной переменной Y. Получим сообщение об ошибке "Free variable in expression".
Второй способ. Добавим в факт отсечение, чтобы в ситуации, когда искомый элемент оказался первым
member3(X,[X|_]):–!.
member3(X,[_|T]):–
member3(X,T).
Заметим, что хотя эта модификация предиката member более эффективна, чем исходная, за счет того, что она не выполняет поиск в хвосте после того, как искомый элемент найден, ее можно использовать только для того, чтобы проверить, имеется ли в списке конкретное значение. Если мы попытаемся применить ее для получения всех
Пример. Создадим предикат, позволяющий соединить два списка в один. Первые два аргумента предиката будут представлять соединяемые списки, а третий — результат соединения.
В качестве основы для решения этой задачи возьмем рекурсию по первому списку. Базисом рекурсии будет факт, устанавливающий, что если присоединить к списку пустой
conc([ ], L, L). /* при присоединении пустого списка
к списку L получим список L */
conc([H|T], L, [H|T1]) :–
conc(T,L,T1). /* соединяем хвост и список L, получаем
хвост результата */
Заметим, что этот предикат также можно применять для решения нескольких задач.
Во-первых, для соединения списков. Например, если задать вопрос
conc([1, 2, 3], [4, 5], X)
то получим в результате
X= [1, 2, 3, 4, 5]
Во-вторых, для того, чтобы проверить, получится ли при объединении двух списков третий. Например, на вопрос:
conc([1, 2, 3], [4, 5], [1, 2, 5]).
ответом будет, конечно, No.
В-третьих, можно использовать этот предикат для разбиения списка на подсписки. Например, если задать следующий вопрос:
conc([1, 2], Y, [1, 2, 3]).
то ответом будет Y=[3].
Аналогично, на вопрос
conc(X, [3], [1, 2, 3]).
получим ответ X=[1, 2].
И, наконец, можно спросить
conc(X, Y, [1, 2, 3]).
Получим четыре решения:
X=[], Y=[1, 2, 3] X=[1], Y=[2, 3] X=[1, 2], Y=[3] X=[1, 2, 3], Y=[]
В-четвертых, можно использовать этот предикат для поиска элементов, находящихся левее и правее заданного элемента. Например, если нас интересует, какие элементы находятся левее и, соответственно, правее числа 2, можно задать следующий вопрос:
conc(L, [2|R], [1, 2, 3, 2, 4]).
Получим два решения:
L=[1], R=[3, 2, 4]. L=[1, 2, 3], R=[4]
В-пятых, на основе нашего предиката conc можно создать предикат, находящий
last(L,X):–
conc(_,[X],L).
Справедливости ради стоит заметить, что этот предикат можно реализовать и "напрямую", без использования предиката conc:
last2([X],X). /* последний элемент одноэлементного
списка — этот элемент */
last2([_|L],X):–
last2(L,X). /* последний элемент списка совпадает
с последним элементом хвоста */
В-шестых, можно определить, используя conc, предикат, позволяющий проверить принадлежность элемента списку. При этом воспользуемся тем, что если элемент принадлежит списку, то
member4(X,L):–
conc(_,[X|_],L).
В-седьмых, используя предикат, позволяющий объединить списки, можно создать предикат, проверяющий по двум значениям и списку, являются ли эти значения
Идея решения заключается в следующем. Если два элемента оказались соседними в списке, значит, этот
neighbors(X,Y,L):–
conc(_,[X,Y|_],L). /* список L получается путем
объединения некоторого списка
со списком, голову которого
составляют элементы X и Y */
Обратите внимание, что этот предикат проверяет только наличие нужных значений в указанном порядке. Если нам неважен порядок, в котором два данных значения встречаются в некотором списке, то следует записать модификацию описанного выше предиката, которая будет проверять оба варианта размещения искомых элементов. Для этого достаточно, чтобы
neighbors2(X,Y,L):–
conc(_,[X,Y|_],L);
conc(_,[Y,X|_],L). /* список L получается
путем объединения некоторого
списка со списком, голову
которого составляют элементы X
и Y или элементы Y и X */
Есть подозрение, что многообразие использований предиката conc приведенными выше примерами не исчерпывается.
Пример. Разработаем предикат, позволяющий "обратить"
Для решения этой задачи воспользуемся рекурсией. Базис: если записать элементы пустого списка (которых нет) в обратном порядке — опять получим пустой
reverse([ ],[ ]). /* обращение пустого списка дает пустой
список*/
reverse([X|T],Z):–
reverse(T,S), conc(S,[X],Z).
/* обращаем хвост и приписываем к нему
справа первый элемент исходного
списка*/
Обратите внимание, что вторым аргументом в предикате conc должен стоять именно одноэлементный [X], а не элемент X. Это связано с тем, что аргументами предиката conc должны быть списки.
Можно написать данный предикат без использования предиката conc. Правда, тогда нам придется добавить дополнительный аргумент, в котором мы будем "накапливать" результат. Мы будем "отщипывать" от исходного списка по элементу и дописывать его к вспомогательному списку. Когда исходный
rev([H|T],L1,L2):–
rev(T,[H|L1],L2). /* голову первого
аргумента дописываем ко
второму аргументу*/
rev([ ],L,L). /* если исходный список закончился,
то второй аргумент — передаем в третий
аргумент в качестве результата*/
Для того чтобы использовать этот предикат обычным "двухаргументным" образом, добавим еще один предикат, который будет запускать наш "основной" предикат rev, имеющий "лишний" аргумент, используемый для накопления элементов обращенного списка. В начале работы второй аргумент должен быть пустым списком.
reverse2(L1,L2):–
rev (L1,[ ],L2).
Пример. Создадим предикат, который позволит проверить, является ли
Первое, что приходит в голову: воспользоваться только что написанным предикатом reverse (или reverse2 ). Перевернуть
palindrom(L):–
reverse (L,L).
Можно решить эту задачу "напрямую", без использования предиката reverse.
Пример. Напишем предикат, позволяющий получать
Решение проведем рекурсией по номеру элемента. В качестве базиса возьмем очевидный факт, что первым N-й N–1 )-м элементом хвоста. Данному определению будет соответствовать следующее предложение:
n_element([X|_],1,X).
n_element([_|L],N,Y):–
N1=N–1,
n_element(L,N1,Y).
Пример. В большинстве практических задач не обойтись без предиката, удаляющего все вхождения заданного значения из списка. Предикат будет зависеть от трех параметров. Первый параметр будет соответствовать удаляемому списку, второй — исходному значению, а третий — результату удаления из первого параметра всех вхождений второго параметра. Создадим его.
Без рекурсии не обойдется и на этот раз. Если первый элемент окажется удаляемым, то нужно перейти к удалению заданного значения из
delete_all(_,[],[]).
delete_all(X,[X|L],L1):–
delete_all (X,L,L1).
delete_all (X,[Y|L],[Y|L1]):–
X<>Y,
delete_all (X,L,L1).
Если нам нужно удалить не все вхождения определенного значения в
Заменим в первом правиле рекурсивный вызов предиката отсечением. В этом случае, пока первый
delete_one(_,[],[]).
delete_one(X,[X|L],L):–!.
delete_one(X,[Y|L],[Y|L1]):–
delete_one(X,L,L1).
В заключение лекции рассмотрим предикат findall, предназначенный для нахождения всех решений некоторой цели. У него три параметра: имя переменной, предикат и
Пример. Посмотрим, как с помощью предиката findall можно решать задачи, подобные тем, которые мы решали в предыдущей лекции.
Найдем имена всех дочек: findall(N,mother(_,N),L). В L попадут имена всех дочек.
Найдем имена всех дочек Даши: findall(N,mother("Даша",N),L). В L попадут имена всех дочек Даши.
В императивных языках, как правило, основной структурой данных являются массивы. В Прологе так же, как и в Лиспе, основным составным типом данных является
Дадим сначала неформальное определение списка.
Будем называть списком упорядоченную последовательность элементов произвольной длины.
[monday, tuesday, wednesday, thursday, friday, saturday, sunday] —
["понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье"] —
[1, 2, 3, 4, 5, 6, 7] —
['п', 'в', 'с', 'ч', 'п', 'с', 'в'] —
[] — пустой
В разделе описания доменов списки описываются следующим образом:
DOMAINS <имя спискового домена>=<имя домена элементов списка>*
Звездочка после имени домена указывает на то, что мы описываем
Например:
listI = integer* /* список, элементы которого —
целые числа */
listR = real* /* список, состоящий из вещественных
чисел */
listC = char* /* список символов */
lists = string* /* список, состоящий из строк */
listL = listI* /* список, элементами которого являются
списки целых чисел */
Последнему примеру будут соответствовать списки вида:
[[1,3,7],[],[5,2,94],[–5,13]]
В классическом Прологе [monday, 1, "понедельник"]
В Турбо Прологе, в связи со
Например, следующее описание:
DOMAINS element = i(integer); c(char); s(string) listE = element*
позволит иметь дело со списками вида
[i(–15), s("Мама"),c('A'),s("мыла"),c('+'),s("раму"),
i(48),c('!')]
Дадим рекурсивное определение списка.
[ ] ) является списком;[H|T] является списком, если H — первый T — Принято называть H T —
Фактически операция "|" позволяет разделить
Данное определение позволяет организовывать рекурсивную обработку списков, разделяя непустой
Например, в списке [1, 2, 3] элемент 1 является головой, а [2, 3] — хвостом, т.е. [1, 2, 3] = [1|[2, 3]].
Заметим, что хвост этого списка [2, 3], в свою очередь, может быть представлен в виде головы 2 и хвоста [3], а [3] можно рассматривать в виде головы 3 и хвоста []. Пустой
В итоге получаем, что [1, 2, 3] эквивалентен списку [1|[2, 3]], который, в свою очередь, эквивалентен списку [1|[2|[3]]]. Последний сопоставим со списком [1|[2|[3|[ ]]]].
В этом же списке можно выделить два первых элемента и хвост из третьего элемента [1,2|[3]]. И, наконец, возможен вариант разбиения на голову из трех первых элементов и пустой хвост: [1, 2, 3|[]].
Чтобы организовать обработку списка, в соответствии с приведенным выше рекурсивным определением, нам достаточно задать предложение (правило или факт, определяющее, что нужно делать с пустым списком), которое будет базисом рекурсии, а также рекурсивное правило, устанавливающее порядок перехода от обработки всего непустого списка к обработке его хвоста. Иногда базис рекурсии записывается не для пустого, а для одно- или двухэлементного списка.
В качестве резюме к нашим рассуждениям запишем еще раз определение списка в нотации Бэкуса–Науэра:
Список ::= [ ]|[Элемент <,Элемент>*]|[Голова|Хвост] Голова ::= Элемент <,Элемент>* Хвост ::= Список
Словесно это можно записать так:
Рассмотрим обработку списков.
Пример. Создадим предикат, позволяющий вычислить длину списка, т.е. количество элементов в списке.
Для решения этой задачи воспользуемся очевидным фактом, что в пустом списке элементов нет, а количество элементов непустого списка, представленного в виде объединения первого элемента и хвоста, равно количеству элементов хвоста, увеличенному на единицу. Запишем эту идею:
length([], 0). /* в пустом списке элементов нет */
length([_|T], L) :–
length(T, L_T), /* L_T — количество
элементов в хвосте */
L = L_T + 1. /* L — количество элементов
исходного списка */
Обратите внимание, что при переходе от всего списка к его хвосту нам не важно, чему равен первый
Разберем на примере, как это будет работать. Пусть нас интересует количество элементов в списке [1,2,3]. Запишем соответствующий вопрос Пролог-системе:
length([1,2,3],X).
Система попытается вначале сопоставить нашу цель с первым предложением length([], 0), однако ей это не удается сделать, потому что первый аргумент цели является непустым списком. Система переходит ко второму предложению процедуры. Сопоставление с заголовком правила проходит успешно, переменная X связывается с переменной L, [1,2,3] будет сопоставлен со списком [_|T], переменная T будет конкретизирована значением [2,3]. Теперь система переходит к попытке достижения подцели length(T,L_T). Как и в предыдущем случае, первое предложение с подцелью не сопоставляется, так как T не пустой. При сопоставлении заголовка правила с подцелью хвост T конкретизируется одноэлементным списком [3]. На следующем шаге рекурсии переменная T означена пустым списком (хвост одноэлементного списка). И, значит, наша подцель выглядит следующим образом: length([], L_T). Эта цель сопоставляется с фактом, переменная L_T становится равной нулю. Раскручивается обратный ход рекурсии: переменная L_T увеличивается на единицу, результат попадает в переменную L. Получаем, что длина списка [3] равна единице. На следующем обратном шаге происходит еще одно добавление единицы, после чего длина списка [2,3] конкретизируется двойкой. И, наконец, на последнем возвратном шаге получаем означивание переменной L числом 3 (количеством элементов в списке [1,2,3] ).
Пример. Создадим предикат, позволяющий проверить принадлежность элемента списку. Предикат будет иметь два аргумента: первый — искомое значение, второй —
Построим данный предикат, опираясь на тот факт, что объект принадлежит списку, если он либо является первым
member(X,[X|_]). /* X — первый элемент списка */
member(X,[_|T]) :–
member(X,T). /* X принадлежит хвосту T*/
Заметим, что в первом случае (когда первый X принадлежит хвосту, нам не важно, какой элемент первый.
Отметим, что описанный предикат можно использовать двояко: во-первых, конечно, для того, для чего мы его и создавали, т.е. для проверки, имеется ли в списке конкретное значение. Мы можем, например, поинтересоваться, принадлежит ли двойка списку [1, 2, 3]:
member(2, [1, 2, 3]).
Получим, естественно, ответ: "Yes".
Подобным образом можно спросить, является ли число 4 [1, 2, 3]:
member(4, [1, 2, 3]).
Ответом, конечно, будет "No".
Второй способ использования данного предиката — это получение по списку его элементов. Для этого нужно в качестве первого аргумента предиката указать свободную переменную. Например:
member(X, [1, 2, 3]).
В качестве результата получим
X=1 X=2 X=3
Третий способ позволит получить по элементу варианты списков, которые могут его содержать. Теперь свободную переменную запишем вторым аргументом предиката, а первым — конкретное значение. Например,
member(1, X).
Вначале Пролог-система выдаст предупреждение о том, что переменная X не связана в первом предложении ( "708 WARNING: The variable is not bound in this clause. (F10=ok, Esc=abort)" ).
У нас есть два способа отреагировать на это предупреждение: нажать кнопку Esc, чтобы отказаться от генерации списков, содержащих единицу в качестве элемента; нажать F10 для того, чтобы продолжить выполнение цели. Во втором случае Пролог-система начнет выдавать варианты списков, содержащих единицу:
X=[1|_] /* единица — первый элемент списка */ X=[_,1|_] /* единица — второй элемент списка */ X=[_,_,1|_] /* единица — третий элемент списка */ и т.д.
Этот процесс будет продолжаться до тех пор, пока не будет нажата комбинация клавиш Ctrl+Break.
Если данный предикат планируется использовать только первым способом, то можно ускорить его работу, устранив поиск элемента в
Первый способ. Добавим в правило проверку на несовпадение первого
member2(X,[X|_]).
member2(X,[Y|T]):–
X<>Y, member2(X,T).
Заметим, что эту модификацию предиката member нельзя использовать для получения всех X будет сравниваться с неозначенной переменной Y. Получим сообщение об ошибке "Free variable in expression".
Второй способ. Добавим в факт отсечение, чтобы в ситуации, когда искомый элемент оказался первым
member3(X,[X|_]):–!.
member3(X,[_|T]):–
member3(X,T).
Заметим, что хотя эта модификация предиката member более эффективна, чем исходная, за счет того, что она не выполняет поиск в хвосте после того, как искомый элемент найден, ее можно использовать только для того, чтобы проверить, имеется ли в списке конкретное значение. Если мы попытаемся применить ее для получения всех
Пример. Создадим предикат, позволяющий соединить два списка в один. Первые два аргумента предиката будут представлять соединяемые списки, а третий — результат соединения.
В качестве основы для решения этой задачи возьмем рекурсию по первому списку. Базисом рекурсии будет факт, устанавливающий, что если присоединить к списку пустой
conc([ ], L, L). /* при присоединении пустого списка
к списку L получим список L */
conc([H|T], L, [H|T1]) :–
conc(T,L,T1). /* соединяем хвост и список L, получаем
хвост результата */
Заметим, что этот предикат также можно применять для решения нескольких задач.
Во-первых, для соединения списков. Например, если задать вопрос
conc([1, 2, 3], [4, 5], X)
то получим в результате
X= [1, 2, 3, 4, 5]
Во-вторых, для того, чтобы проверить, получится ли при объединении двух списков третий. Например, на вопрос:
conc([1, 2, 3], [4, 5], [1, 2, 5]).
ответом будет, конечно, No.
В-третьих, можно использовать этот предикат для разбиения списка на подсписки. Например, если задать следующий вопрос:
conc([1, 2], Y, [1, 2, 3]).
то ответом будет Y=[3].
Аналогично, на вопрос
conc(X, [3], [1, 2, 3]).
получим ответ X=[1, 2].
И, наконец, можно спросить
conc(X, Y, [1, 2, 3]).
Получим четыре решения:
X=[], Y=[1, 2, 3] X=[1], Y=[2, 3] X=[1, 2], Y=[3] X=[1, 2, 3], Y=[]
В-четвертых, можно использовать этот предикат для поиска элементов, находящихся левее и правее заданного элемента. Например, если нас интересует, какие элементы находятся левее и, соответственно, правее числа 2, можно задать следующий вопрос:
conc(L, [2|R], [1, 2, 3, 2, 4]).
Получим два решения:
L=[1], R=[3, 2, 4]. L=[1, 2, 3], R=[4]
В-пятых, на основе нашего предиката conc можно создать предикат, находящий
last(L,X):–
conc(_,[X],L).
Справедливости ради стоит заметить, что этот предикат можно реализовать и "напрямую", без использования предиката conc:
last2([X],X). /* последний элемент одноэлементного
списка — этот элемент */
last2([_|L],X):–
last2(L,X). /* последний элемент списка совпадает
с последним элементом хвоста */
В-шестых, можно определить, используя conc, предикат, позволяющий проверить принадлежность элемента списку. При этом воспользуемся тем, что если элемент принадлежит списку, то
member4(X,L):–
conc(_,[X|_],L).
В-седьмых, используя предикат, позволяющий объединить списки, можно создать предикат, проверяющий по двум значениям и списку, являются ли эти значения
Идея решения заключается в следующем. Если два элемента оказались соседними в списке, значит, этот
neighbors(X,Y,L):–
conc(_,[X,Y|_],L). /* список L получается путем
объединения некоторого списка
со списком, голову которого
составляют элементы X и Y */
Обратите внимание, что этот предикат проверяет только наличие нужных значений в указанном порядке. Если нам неважен порядок, в котором два данных значения встречаются в некотором списке, то следует записать модификацию описанного выше предиката, которая будет проверять оба варианта размещения искомых элементов. Для этого достаточно, чтобы
neighbors2(X,Y,L):–
conc(_,[X,Y|_],L);
conc(_,[Y,X|_],L). /* список L получается
путем объединения некоторого
списка со списком, голову
которого составляют элементы X
и Y или элементы Y и X */
Есть подозрение, что многообразие использований предиката conc приведенными выше примерами не исчерпывается.
Пример. Разработаем предикат, позволяющий "обратить"
Для решения этой задачи воспользуемся рекурсией. Базис: если записать элементы пустого списка (которых нет) в обратном порядке — опять получим пустой
reverse([ ],[ ]). /* обращение пустого списка дает пустой
список*/
reverse([X|T],Z):–
reverse(T,S), conc(S,[X],Z).
/* обращаем хвост и приписываем к нему
справа первый элемент исходного
списка*/
Обратите внимание, что вторым аргументом в предикате conc должен стоять именно одноэлементный [X], а не элемент X. Это связано с тем, что аргументами предиката conc должны быть списки.
Можно написать данный предикат без использования предиката conc. Правда, тогда нам придется добавить дополнительный аргумент, в котором мы будем "накапливать" результат. Мы будем "отщипывать" от исходного списка по элементу и дописывать его к вспомогательному списку. Когда исходный
rev([H|T],L1,L2):–
rev(T,[H|L1],L2). /* голову первого
аргумента дописываем ко
второму аргументу*/
rev([ ],L,L). /* если исходный список закончился,
то второй аргумент — передаем в третий
аргумент в качестве результата*/
Для того чтобы использовать этот предикат обычным "двухаргументным" образом, добавим еще один предикат, который будет запускать наш "основной" предикат rev, имеющий "лишний" аргумент, используемый для накопления элементов обращенного списка. В начале работы второй аргумент должен быть пустым списком.
reverse2(L1,L2):–
rev (L1,[ ],L2).
Пример. Создадим предикат, который позволит проверить, является ли
Первое, что приходит в голову: воспользоваться только что написанным предикатом reverse (или reverse2 ). Перевернуть
palindrom(L):–
reverse (L,L).
Можно решить эту задачу "напрямую", без использования предиката reverse.
Пример. Напишем предикат, позволяющий получать
Решение проведем рекурсией по номеру элемента. В качестве базиса возьмем очевидный факт, что первым N-й N–1 )-м элементом хвоста. Данному определению будет соответствовать следующее предложение:
n_element([X|_],1,X).
n_element([_|L],N,Y):–
N1=N–1,
n_element(L,N1,Y).
Пример. В большинстве практических задач не обойтись без предиката, удаляющего все вхождения заданного значения из списка. Предикат будет зависеть от трех параметров. Первый параметр будет соответствовать удаляемому списку, второй — исходному значению, а третий — результату удаления из первого параметра всех вхождений второго параметра. Создадим его.
Без рекурсии не обойдется и на этот раз. Если первый элемент окажется удаляемым, то нужно перейти к удалению заданного значения из
delete_all(_,[],[]).
delete_all(X,[X|L],L1):–
delete_all (X,L,L1).
delete_all (X,[Y|L],[Y|L1]):–
X<>Y,
delete_all (X,L,L1).
Если нам нужно удалить не все вхождения определенного значения в
Заменим в первом правиле рекурсивный вызов предиката отсечением. В этом случае, пока первый
delete_one(_,[],[]).
delete_one(X,[X|L],L):–!.
delete_one(X,[Y|L],[Y|L1]):–
delete_one(X,L,L1).
В заключение лекции рассмотрим предикат findall, предназначенный для нахождения всех решений некоторой цели. У него три параметра: имя переменной, предикат и
Пример. Посмотрим, как с помощью предиката findall можно решать задачи, подобные тем, которые мы решали в предыдущей лекции.
Найдем имена всех дочек: findall(N,mother(_,N),L). В L попадут имена всех дочек.
Найдем имена всех дочек Даши: findall(N,mother("Даша",N),L). В L попадут имена всех дочек Даши.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.