Избегайте двойного рекурсивного вызова в Прологе
У меня есть этот код Пролога:
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.