正如@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 了。例如,询问所有带有 20
和 31
的列表:
?- 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 一起使用,那当然没问题。然而,花时间在一些其他的查询上花点时间可能是值得的。例如最一般的查询:M
0
's 和N
1
'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
's 和1
's:
?- 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
andN is 3
之类的目标放在实际调用 count_digits/3 之后。这样,您就要求 的所有解决方案count_digits(L,M,N)
,其中有无限多个,然后在后续目标中过滤掉满足您的约束(M is 2
和N is 3
)的目标。通过目标的这种排序,您可以确保 control_number/1 在产生有限数量的解决方案后不会终止,因为第一个目标会产生无限多的解决方案候选者,这些目标随后会根据您的约束而失败。相反,首先放置此类约束或将它们直接作为参数放入@false 发布的目标中。