Как получить сумму заданных чисел в prolog?

Я новичок в prolog, и я делаю некоторые упражнения для практики. Поэтому я пытаюсь получить сумму данных чисел в списке. Я пытаюсь использовать это:

my_last(X, [X]).  
my_last(X, [_|L]) :- my_last(X, L).

(от здесь)

как мой гид. Итак, это мой код для получения суммы:

listsum(X, []).                  
listsum(X, [H|L]):-
    X is H + listsum(X, L).

когда я скомпилировать его, он говорит

практика.pl: 3: оценочный listsum(_G139,_G140) не существует
практика.pl: 2: одноэлементные переменные: [X]

тогда, когда я попытаюсь listsum(0, [1,2,3]). возвращает false.

Я все еще не очень понимаю пролог, а также список и рекурсию в прологе.

2 ответов


арифметика

как вы уже обнаружили, арифметика может быть обработана в прологе с помощью (is)/2 оператора. Это потому, что в прологе все является только символическим исчислением: вещи не имеют значения по умолчанию, поэтому объединение (=)/2 не знаю (+)/2 относится к добавлению, например.

Итак, ваша проблема в том, что вы используете обычный предикат внутри (is)/2 (вот ваш рекурсивный вызов). С (is)/2 выполняет только арифметические, он не оценивает вызов предиката. Он даже не распознает его, так как это не арифметическая функция.

исправление здесь будет влиять на результат рекурсивного вызова переменной, а затем использовать его в (is)/2 звоните:

listsum(X,[]).
listsum(Result, [Head|Tail]) :-
    listsum(SumOfTail, Tail),
    Result is Head + SumOfTail.

базовый вариант корректность

но если вы протестируете этот код, вы не получите желаемого результата. Причина в том, что у вас есть другая проблема, в своем базовом варианте. Сумма пустого списка не является "ничем", как вы заявил пишу

listsum(X,[]).

(X является свободной переменной, следовательно, может быть что угодно).

, это 0:
listsum(0, []).

результирующий код:

listsum(0, []).
listsum(Result, [Head|Tail]) :-
    listsum(SumOfTail, Tail),
    Result is Head + SumOfTail.

порядок аргументов

теперь, как sidenote, в Prolog соглашение заключается в том, что выходные переменные должны быть помещены в конец предиката, а входные переменные должны быть помещены в начало предиката, поэтому, чтобы вести себя так, как мы хотели, мы могли бы рефакторировать как следует:

listsum([], 0).
listsum([Head|Tail], Result) :-
    listsum(Tail, SumOfTail),
    Result is Head + SumOfTail.

Оптимизация Хвостового Вызова

теперь мы все еще можем улучшить этот предикат с помощью более продвинутых методов. Например, мы могли бы ввести хвостовые вызовы, чтобы можно было выполнить оптимизацию хвостовых вызовов (googlable), благодаря идиоме декларативного программирования, называемой аккумулятором:

listsum(List, Sum) :-
    listsum(List, 0, Sum).

listsum([], Accumulator, Accumulator).

listsum([Head|Tail], Accumulator, Result) :-
    NewAccumulator is Accumulator + Head,
    listsum(Tail, NewAccumulator, Result).

идея заключается в том, чтобы обновлять промежуточный результат на каждом шаге рекурсии (добавляя значение текущей главы списка к it), а затем просто укажите, что когда список пуст, это промежуточное значение является конечным значением.

получение более общих программ

как вы могли заметить, в прологе, довольно часто предикаты могут быть использованы несколькими способами. Например, length/2 можно использовать для обнаружения длины списка:

?- length([1, 2, 3], Length).
Length = 3.

или построить список скелетов со свободными переменными нужной длины:

?- length(List, 3).
List = [_G519, _G522, _G525].

здесь, однако, вы могли бы отметить, что вы не можете спросить Prolog каковы списки, которые имеют сумму, которая является 6:

?- listsum(L, 6).
ERROR: is/2: Arguments are not sufficiently instantiated

это потому, что, чтобы" вернуться назад", Пролог должен был бы решить уравнение, когда приходит вызов (is)/2 оператора. И хотя ваш прост (только дополнения), арифметика не разрешима таким образом в общем случае.

чтобы преодолеть эту проблему, можно использовать Программирование ограничений. Очень хорошая библиотека доступна для SWI, clpfd.

синтаксис здесь быть:

:- use_module(library(clpfd)).

listsum(List, Sum) :-
    listsum(List, 0, Sum).

listsum([], Accumulator, Accumulator).

listsum([Head|Tail], Accumulator, Result) :-
    NewAccumulator #= Accumulator + Head,
    listsum(Tail, NewAccumulator, Result).

теперь мы можем использовать наш предикат другим способом, который мы хотели бы использовать:

?- listsum(L, 6).
L = [6] ;
L = [_G1598, _G1601],
_G1598+_G1601#=6 ;
L = [_G1712, _G1715, _G1718],
_G1712+_G1715#=_G1728,
_G1728+_G1718#=6 . % Here I interrupted the answer but it would not terminate.

мы могли бы даже попросить все решения проблемы:

?- listsum(L, X).
L = [],
X = 0 ;
L = [X],
X in inf..sup ;
L = [_G2649, _G2652],
_G2649+_G2652#=X . % Here I interrupted the answer but it would not terminate

я только что упомянул это, чтобы вы поняли, что довольно часто используют (is)/2 следует избегать и использовать Программирование ограничений следует предпочесть, чтобы получить наиболее общие программы.


Если возможно, используйте clpfd вместо простого старого (is)/2 (и друзей).

clpfd предлагает логически чистый предикат sum/3 что может соответствовать вашим потребностям!