Существует книга о языке Lisp, в которой говорится: список – это упорядоченная последовательность элементов, где слово упорядоченная означает, что порядок перечисления элементов имеет значение. В Prolog список заключается в квадратные скобки и обычно имеет голову (head) и хвост (tail):
| Список | Тип | Голова | Хвост |
| [3, 4, 5, 6, 7] | Integer | 3 | [4, 5, 6, 7] |
| ["wo3", "ni3", "ta1"] | String | "wo3" | ["ni3", "ta1"] |
| [4] | Integer | 4 | [] |
| [3.4, 5.6, 2.3] | Real | 3.4 | [5.6, 2.3] |
Вы можете соотнести образец переменных со списком. Например, если вы соотнесете шаблон
[X|Xs] со списком [3.4, 5.6, 2.3], вы получите, что X = 3.4 и Xs = [5.6, 2.3], то есть, что переменная X соответствует голове списка, а переменная Xs соответствует хвосту. Конечно, вы можете использовать другую пару переменных вместо X и Xs в шаблоне [X|Xs]. Так, шаблоны [A|B], [X|L], [Head|Tail], [First|Rest] и [P|Q] являются эквивалентными шаблонами. Вот ещё несколько примеров соответствий для шаблона [X|Xs]:
| Шаблон | Список | X | Xs |
| [X|Xs] | [3.4, 5.6, 2.3] | 3.4 | [5.6, 2.3] |
| [X|Xs] | [5.6, 2.3] | 5.6 | [2.3] |
| [X|Xs] | [2.3] | 2.3 | [] |
| [X|Xs] | [] | Не соответствуют |
Как вы можете видеть, шаблон
[X|Xs] соответствует списку, содержащему как минимум один элемент. Вот шаблон, который соответствует спискам, содержащим по крайней мере два элемента:| Шаблон | Список | X1, X2 | Xs |
| [X1, X2|Xs] | [3, 5, 2, 7] | X1 = 3, X2 = 5 | [2, 7] |
| [X1, X2|Xs] | [2, 7] | X1 = 2, X2 = 7 | [] |
| [X1, X2|Xs] | [7] | Не соответствуют | |
| [X1, X2|Xs] | [] | Не соответствуют |
Теперь создадим новый консольный проект и рассмотрим пример, который будет проверять наличие числа в списке чисел. В файл main.pro добавим следующий код:
implement main
open core, console
clauses
member(Number, [Number | _ ]).
member(Number, [ _ | Tail]) :- member(Number,Tail).
run():-
init(),
write("Введите список в квадратных скобках с запятыми"), nl,nl,
write("Список -> "),
List = read(), nl, % Список чисел
write("Число -> "),
Chislo = read(), nl,
member(Chislo,List), !, % Проверка наличия числа в списке
write(Chislo, " есть в списке ", List), nl, nl,
_ = readchar(),
write("Конец. Нажмите любую клавишу "),
_ = readchar().
run() :-
write("Числа в списке нет "),
_= readchar(), nl, nl,
write("Конец. Нажмите Enter"),
_= readchar().
end implement main
goal
mainExe::run(main::run).
В приведенном выше коде используется предикат объекта member. Его нужно задавать в файле класса main.cl:
class main
open core
predicates
member : (integer, integer*) nondeterm (i,i) (o,i).
run : core::runnable.
end class main
Рассмотрим два правила, которые отвечают за поиск введенного числа в списке:
member(Number, [Number | _ ]).
member(Number, [ _ | Tail]) :- member(Number,Tail).
Словами это можно описать так:
• переменная
Number принадлежит списку, если голова списка есть Number (первое правило);
• иначе
Number принадлежит списку, если данная переменная принадлежит хвосту (второе правило).
Приведем еще несколько предикатов, которые реализуют различные операции над списками:
class predicates
append:(integer*,integer*,integer*) procedure (i, i, o).
sort:(integer*,integer*) procedure (i, o).
insert:(integer,integer*,integer*) procedure (i, i, o).
asc_order:(integer,integer) determ (i, i).
clauses
/* Слияние списков: все три аргумента списки, третий список результат слияния первых двух списков */
append([],L,L).
append([N|L1],L2,[N|L3]):- append(L1,L2,L3).
/* Сортировка списков: первый аргумент исходный список, второй аргумент отсортированный по возрастанию список.*/
/*Реализована сортировка вставкой.*/
sort([],[]).
sort([X|T],L):-sort(T,L1), insert(X,L1,L).
insert(X,[Y|L],[Y|L1]):- asc_order(X,Y),!, insert(X,L,L1).
/* вставляет первый аргумент в список, заданный в качестве второго аргумента, результат третий аргумент */
insert(X,L,[X|L]).
/* вспомогательный предикат */
asc_order(X,Y):- X>Y.
Рассмотрим теперь, как подсчитать число элементов в списке, или какова длина списка. Логично определить:
• длина пустого списка [] есть 0;
• длина любого другого списка есть 1 плюс длина его хвоста.
Для реализации этого на языке Prolog создадим новый проект и в файле main.pro объявим предикат
len в разделе class predicates:
len:(integer*, ;integer) procedure (i, o).
А также добавим правила, описывающие вышесказанное:
len([], 0) :- !.
len([_X|Xs], 1+S) :- len(Xs, S).
Строго говоря, [_X|Xs] сопоставляется с любым непустым списком, связывая Xs с хвостом списка. Значение головы неважно, если она есть, она может быть учтена как один элемент.
Модифицируем нашу программу добавив в неё возможность нахождения суммы и произведения элементов списка. Таким образом, код будет иметь вид:
implement main
open core, console
class predicates
len:(integer*, integer) procedure (i, o).
sum:(integer*, integer) procedure (i, o).
prod:(integer*, integer) procedure (i, o).
clauses
len([], 0) :- !.
len([_X|Xs], 1+S) :- len(Xs, S).
sum([], 0) :- !.
sum([X|Xs], S+X) :- sum(Xs, S).
prod([], 1) :- !.
prod([X|Xs], P*X) :- prod(Xs, P).
run():-
init(),
write("Введите список в квадратных скобках с запятыми"), nl,nl,
write("Список -> "),
List = read(), nl, % Список чисел
_ = readchar(),
len(List, A),
write("Длинна списка: ", A), nl, nl,
sum(List, S),
write("Сумма элементов списка: ", S), nl, nl,
prod(List, P),
write("Произведение элементов списка: ", P), nl, nl,
write("Конец. Нажмите любую клавишу "),
_ = readchar().
end implement main
goal
mainExe::run(main::run).
Давайте посмотрим, что произойдёт, если вызвать sum([3, 5, 2], S).
1. Вызов
sum([3, 5, 2], S) соответствует предложению sum([X|Xs], S+X) :- sum(Xs, S), где X = 3, Xs = [5, 2], возвращая sum([3, 5, 2], S[5, 2]+3) :- sum([5, 2], S[5, 2])
2. Вызов
sum([5, 2], S[5, 2]) соответствует предложению sum([X|Xs], S+X) :- sum(Xs, S), где X = 5, Xs = [2], возвращая sum([5, 2], S[2]+5) :- sum([2], S[2])
3. Вызов
sum([2], S[2]) соответствует предложению sum([X|Xs], S+X) :- sum(Xs, S), где X = 2, Xs = [], возвращая sum([2], S[]+2) :- sum([], S[])
4. Вызов
sum([], S[]), соответствует предложению sum([], 0) :- !, возвращая S[]=0.
Достигнув конца списка, компьютер должен откатиться к началу вычислений. Что еще хуже, он должен сохранить каждое значение переменной
X, которое он найдет по пути, чтобы произвести сложение S+X во время возвращения. Традиционный путь предотвращения этого – использование накопителя. Вот определение, которое использует накопитель для суммирования элементов списка:
add([], A, A).
add([X|Xs], A, S) :- add(Xs, X+A, S).
Давайте посмотрим, как компьютер использует второй вариант суммирования элементов списка.
1.
add([3, 5, 2], 0, S) соответствует предложению add([X|Xs], A, S) :- add(Xs, X+A, S), возвращая add([5, 2], 0+3, S)
2.
add([5, 2], 0+3, S) соответствует предложению add([X|Xs], A, S) :- add(Xs, X+A, S) возвращая add([2], 0+3+5, S)
3.
add([2], 0+3+5, S) соответствует предложению add([X|Xs], A, S) :- add(Xs, X+A, S) возвращая add([], 0+3+5+2, S), что соответствует предложению add([], A, A), возвращая S = 10.
Предикат
add можно использовать для вычисления среднего арифметического значений элементов списка чисел, задав дополнительно такое правило:
avg(Xs, A/L) :- sum(Xs, A), len(Xs, L).
class predicates
member : (integer, integer*) nondeterm (i,i) (o,i).
Так будет проще.
Ну и здесь можно было добавить, что вместо предиката member(Chislo,List), можно было использовать конструкцию: Chislo in List.