Сумма элементов в списке в Prolog

list_sum([], 0).
list_sum([Head | Tail], TotalSum) :-
    list_sum(Tail, Sum1),
    Total = Head + Sum1.

этот код возвращает true. Если я заменю Total = Head + Sum1 С Total is Head + Sum1, затем он вернет значение. Но что я должен заменить, чтобы получить такой результат:

?- list_sum([1,2,0,3], Sum).
Sum = 1+2+0+3 ; % not to return value 6!!!

5 ответов


обратите внимание,что во втором предложении вашей процедуры TotalSum никогда не создается. Вы должны были получить предупреждение от переводчика при консультации с вашим кодом.

вот мое предложение:

list_sum([Item], Item).
list_sum([Item1,Item2 | Tail], Total) :-
    list_sum([Item1+Item2|Tail], Total).

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

второе предложение касается шага рекурсии. Он захватывает первые два элемента списка и выполняет рекурсивный вызов replacing эти два элемента с новым термином Item1+Item2.


ответ прост:

sum_list([], 0).
sum_list([H|T], Sum) :-
   sum_list(T, Rest),
   Sum is H + Rest.

этот код работает только в одном направлении - это значит - она не позволяет создавать списки с этой конкретной суммы. Но поскольку набор таких списков бесконечен, это все равно было бы непрактично.


В Пролог (+)/2 - это двоичный infix оператора. Это позволяет нам писать A+B вместо +(A,B).

?- current_op(_,yfx,+).   % left-associative binary infix operator
true.

(+)/2 партнеры слева, так что 1+2+3 это сокращение от (1+2)+3.

(.)/2 присоединяется к право, так что [1,2,3] сокращенно .(1,.(2,.(3,[]))).

чтобы получить parenthesization право, мы используем вспомогательный предикат с собой дополнительный "аккумулятор" аргумент:

list_sum([X|Xs],S) :-
   list_sum0_sum(Xs,X,S).

list_sum0_sum([],    S ,S).
list_sum0_sum([X|Xs],S0,S) :-
   list_sum0_sum(Xs,S0+X,S).

образец запрос:

?- list_sum([1,2,0,3],S).
S = 1+2+0+3.

Если вы хотите преобразовать список чисел в аддитивное выражение, из

[1,2,3]

to

1 + 2 + 3

вы можете сделать что-то вроде этого, используя что-то вроде список разница:

list_to_additive_expr( [] , 0 ).
list_to_additive_expr( [X|Xs] , X + RHS ) :-
  sum_of( Xs , RHS ).

кроме того, вы можете использовать аккумулятор:

list_to_additive_expr( Xs , Expr ) :-
  list_to_additive_expr( Xs , 0 , Expr )
  .

list_to_additive_expr( []     , Expr , Expr ) .
list_to_additive_expr( [X|Xs] , RHS , Expr ) :-
  sum_of( Xs , X + RHS , Expr )
  .

Я считаю, что вы найдете первый стиль не правильно хвост рекурсивной и поэтому не будет оптимизирован в цикл via оптимизация хвостовой рекурсии (TRO) - и поэтому, если список достаточно длинный, получит переполнение стека. Второй подход должен быть применен TRO и должен работать для списков любой длины.

что такое ТРО, спросите вы? Вот!--24-->Википедия с ответом для вас:

в информатике вызов хвоста-это вызов подпрограммы, который происходит внутри другого процедура и которая производит возвращаемое значение, которое после этого немедленно возвращенный вызывающая процедура. Затем говорят, что сайт вызова находится в хвостовом положении, т. е. в конце вызывающая процедура. Если подпрограмма выполняет хвостовой вызов для себя, она вызывается хвост-рекурсивный. Это частный случай рекурсии.

хвостовые вызовы значительны, потому что они могут быть реализованы без добавления нового стека кадр в стек вызовов. Большая часть рамок текущей процедуры не требуется больше, и его можно заменить рамкой хвоста вызов, измененный соответствующим образом (аналогично overlay для процессов, но для вызовов функций). Затем программа может перейти к называемая подпрограмма. Создание такого кода вместо стандартной последовательности вызовов называется устранение хвостового вызова или оптимизация хвостового вызова.


программа

list_sum([],0).

list_sum([Head|Tail], TotalSum):-
list_sum(Tail, Sum1),
TotalSum is Head+Sum1.

Теперь, если запрос

?- list_sum([1,2,3,4], Sum).

ответ

Sum = 10