Избегайте двойного рекурсивного вызова в Прологе

У меня есть этот код Пролога:

f([ ],-1).

f([H|T],S):- f(T,S1), S1>0, !, S is S1+H.

f([_|T],S):- f(T,S1), S is S1.

Как мне избежать второго (избыточного) рекурсивного вызова f(T,S1), в третьем предложении общий эффект предиката остается таким же?

Я знаю, что это можно сделать, определив дополнительный предикат.

Как можно определить такой предикат?

2 ответа

Решение

Сначала мы его немного переписываем, чтобы лучше увидеть сходство,

f([ ],-1).
f([H|T],S) :- f(T,S1), S1>0, !, S is S1+H.
f([H|T],S) :- f(T,S1),          S is S1.

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

f([ ],-1).
f([H|T],S) :- f(T,S1), additional_predicate(S1,S,H).

Теперь все, что осталось сделать, это написать те же цели под новым именем:

additional_predicate(S1,S,H):- S1>0, !, S is S1+H.
additional_predicate(S1,S,H):- S is S1.

Вот и все.

Проще говоря, после звонка f(T,S1) сделано, S1 уже рассчитан, поэтому нет необходимости пересчитывать его заново.

| ?- length(L,_), f(L,R).
   L = [],
   R = -1
;  L = [_A],
   R = -1
;  L = [_A,_B],
   R = -1
;  L = [_A,_B,_C],
   R = -1
;  L = [_A,_B,_C,_D],
   R = -1
;  L = [_A,_B,_C,_D,_E],
   R = -1
;  L = [_A,_B,_C,_D,_E,_F],
   R = -1
;  L = [_A,_B,_C,_D,_E,_F,_G],
   R = -1
;  L = [_A,_B,_C,_D,_E,_F,_G,_H],
   R = -1
;  L = [_A,_B,_C,_D,_E,_F,_G,_H,_I],
   R = -1
;  L = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J],
   R = -1 ...

Это звонит в колокол?

Что ж, вот что я отвечу:

f(L, -1) :-
   length(L, _).

Хотя теперь это прекращается и не удается f(L, 0)тогда как исходное определение зацикливалось. Если вы настаиваете и на этой эквивалентности:

f(L, MinusOne) :-
   length(L, _),
   MinusOne = -1.
Другие вопросы по тегам