我们可以通过归纳定义来定义这个预测。 Subgroup 是 Group 的子组,如果:
-
Subgroup 是一个空列表;
-
Subgroup 的第一个元素与Group 的第一个元素相同,Subgroup 的其余部分是Group 其余部分的子组;
-
Subgroup 是 Group 其余部分的子组。
我们需要相应地更新N,这样,如果Subgroup为空,则长度为0:
isSubgroup([], _, 0). %% (1)
isSubgroup([H|TS], [H|TG], N) :- %% (2)
N1 is N-1,
isSubgroup(TS, TG, N1).
isSubgroup(S, [_|TG], N) :- %% (3)
isSubgroup(S, TG, N).
然而,上述结果导致 same 子组的重复值。这是因为我们可以通过多种方式满足谓词。例如,如果我们调用:
isSubgroup([], [1,2], 0).
那么它通过事实 (1) 得到满足,但最后一个子句 (3) 也用 isSubgroup([], [1], 0). 调用它,然后它将通过事实 (1) 得到满足,等等。
我们可以通过使最后一个子句更具限制性来避免这种情况:
isSubgroup([], _, 0). %% (1)
isSubgroup([H|TS], [H|TG], N) :- %% (2)
N1 is N-1,
isSubgroup(TS, TG, N1).
isSubgroup([HS|TS], [_|TG], N) :- %% (3)
isSubgroup([HS|TS], TG, N).
以上适用于给定的“方向”(所有参数都应接地,都是“输入”)。但通常人们也想在其他方向使用谓词。当我们使用参数作为“输出”时,我们可以实现一个基本工作的版本,并且仍然使用 tail-call optimization (TCO):
isSubgroup(S, G, N) :-
isSubgroup(S, G, 0, N).
isSubgroup([], _, L, L). %% (1)
isSubgroup([H|TS], [H|TG], L, N) :- %% (2)
L1 is L+1,
isSubgroup(TS, TG, L1, N).
isSubgroup([HS|TS], [_|TG], L, N) :- %% (3)
isSubgroup([HS|TS], TG, L, N).
例如:
?- isSubgroup([1,4,2], G, N).
G = [1, 4, 2|_2974],
N = 3 ;
G = [1, 4, _2972, 2|_2986],
N = 3 ;
G = [1, 4, _2972, _2984, 2|_2998],
N = 3 ;
G = [1, 4, _2972, _2984, _2996, 2|_3010],
N = 3 .
因此,Prolog 能够提出以[1,4,2] 为子组的组,并且能够确定子组的长度N。
我们也可以反方向查询:
?- isSubgroup(S, [1,4,2], N).
S = [],
N = 0 ;
S = [1],
N = 1 ;
S = [1, 4],
N = 2 ;
S = [1, 4, 2],
N = 3 ;
S = [1, 2],
N = 2 ;
S = [4],
N = 1 ;
S = [4, 2],
N = 2 ;
S = [2],
N = 1 ;
false.
Prolog 可以针对给定组 [1,4,2] 穷举枚举所有可能的子组,以及 N 该子组的长度。