正如@false 已经指出的那样,这个谓词非常适合clpfd。除此之外,我添加了约束(标记为% <-)以确保M 和N 在递归情况下大于0,因此一旦这些变量减少到,Prolog 就不会继续寻找进一步的解决方案0.
:- use_module(library(clpfd)).
count_digits([], 0, 0).
count_digits([1|T], M, N) :-
N #> 0, % <-
NRec #= N-1,
count_digits(T, M, NRec).
count_digits([0|T], M, N) :-
M #> 0, % <-
MRec #= M-1,
count_digits(T, MRec, N).
通过这些小的修改,您已经可以通过多种方式使用 count_digits/3。例如,询问所有包含 2 个0 和 3 个1 的列表:
?- count_digits(L,2,3).
L = [1,1,1,0,0] ? ;
L = [1,1,0,1,0] ? ;
L = [1,1,0,0,1] ? ;
L = [1,0,1,1,0] ? ;
L = [1,0,1,0,1] ? ;
L = [1,0,0,1,1] ? ;
L = [0,1,1,1,0] ? ;
L = [0,1,1,0,1] ? ;
L = [0,1,0,1,1] ? ;
L = [0,0,1,1,1] ? ;
no
或计算给定列表中0 和1 的出现次数:
?- count_digits([1,1,0,0,1],M,N).
M = 2,
N = 3
% 1
或者甚至在包含变量的列表中询问0 和1 的数量:
?- count_digits([1,0,X,Y],M,N).
M = X = Y = 1,
N = 3 ? ;
M = N = 2,
X = 1,
Y = 0 ? ;
M = N = 2,
X = 0,
Y = 1 ? ;
M = 3,
N = 1,
X = Y = 0
这已经很不错了,人们可能会对谓词感到满意。如果您打算按照@false 的建议将它与 control_number/1 一起使用,那当然没问题。然而,花时间在一些其他的查询上花点时间可能是值得的。例如最一般的查询:M0's 和 N1's 有哪些列表?
?- count_digits(L,M,N).
L = [],
M = N = 0 ? ;
L = [1],
M = 0,
N = 1 ? ;
L = [1,1],
M = 0,
N = 2 ? ;
L = [1,1,1],
M = 0,
N = 3 ?
...
它只生成包含1 的列表。这是因为第一个递归规则是描述以1 作为列表的第一个元素的情况的规则。因此,解决方案的顺序不公平。以下查询的结果可能更不直观:哪些列表具有相同(但不固定)数量的0 和1:
?- count_digits(L,M,M).
L = [],
M = 0 ? ;
有一个答案,然后谓词循环。这并不是一个理想的属性。关于这个查询的一个有趣的观察:如果在固定长度的列表上使用它,结果实际上是预期的:
?- length(L,_), count_digits(L,M,M).
L = [],
M = 0 ? ;
L = [1,0],
M = 1 ? ;
L = [0,1],
M = 1 ? ;
L = [1,1,0,0],
M = 2 ? ;
L = [1,0,1,0],
M = 2 ? ;
...
将这个想法应用于前面的查询会产生公平的结果排序:
?- length(L,_), count_digits(L,M,N).
L = [],
M = N = 0 ? ;
L = [1],
M = 0,
N = 1 ? ;
L = [0],
M = 1,
N = 0 ? ;
L = [1,1],
M = 0,
N = 2 ? ;
L = [1,0],
M = N = 1 ? ;
...
不必在前面加上辅助目标就可以得到这些结果当然很好。再仔细看看 count_digits/3 所描述的关系,另一个观察结果令人眼前一亮:如果有 M 0's 和 N 1's 列表的长度实际上是固定的,即M+N。为了使这些观察结果发挥作用,可以将 count_digits/3 重命名为 list_0s_1s/3 并将 count_digits/3 重新定义为具有以下约束的调用谓词:
:- use_module(library(clpfd)).
count_digits(L,M,N) :-
X #= M+N,
length(L,X), % L is of length M+N
list_0s_1s(L,M,N).
list_0s_1s([], 0, 0).
list_0s_1s([1|T], M, N) :-
N #> 0,
NRec #= N-1,
list_0s_1s(T, M, NRec).
list_0s_1s([0|T], M, N) :-
M #> 0,
MRec #= M-1,
list_0s_1s(T, MRec, N).
上面的前三个查询产生与以前相同的结果,但是这两个现在产生的结果是公平的,没有循环:
?- count_digits(L,M,N).
L = [],
M = N = 0 ? ;
L = [1],
M = 0,
N = 1 ? ;
L = [0],
M = 1,
N = 0 ? ;
L = [1,1],
M = 0,
N = 2 ? ;
L = [1,0],
M = N = 1 ?
...
?- count_digits(L,M,M).
L = [],
M = 0 ? ;
L = [1,0],
M = 1 ? ;
L = [0,1],
M = 1 ? ;
L = [1,1,0,0],
M = 2 ? ;
L = [1,0,1,0],
M = 2 ?
...
关于谓词 control_number/1 的最后两个注意事项:首先,如果您使用 is/2,请确保像这样使用它:
?- M is 2.
M = 2
% 1
而不是(如您对 control_number/1 的定义中所使用的):
?- 2 is M.
ERROR!!
INSTANTIATION ERROR- in arithmetic: expected bound value
% 1
其次,如果您打算使用诸如 control_number/1 之类的谓词来调用 count_digits/3,请不要将诸如 M is 2 和 N is 3 之类的目标放在实际调用 count_digits/ 之后 3.这样,您就要求count_digits(L,M,N) 的所有解决方案,其中有无限多个,然后在后续目标中过滤出满足您的约束的目标(M is 2 和N is 3)。通过目标的这种排序,您可以确保 control_number/1 在产生有限数量的解决方案后不会终止,因为第一个目标会产生无限多的解决方案候选者,这些目标随后会根据您的约束而失败。相反,首先放置此类约束或将它们直接作为参数放入@false 发布的目标中。