我想写一个谓词,它可以计算所有遇到的数字:
count(1, [1,0,0,1,0], X).
X = 2.
我试着把它写成:
count(_, [], 0).
count(Num, [H|T], X) :- count(Num, T, X1), Num = H, X is X1 + 1.
为什么不起作用?
答案 0 :(得分:7)
为什么不起作用?
Prolog是一种编程语言,通常可以直接回答这个问题。看看我如何从失败的查询开始尝试你的定义:
?- count(1, [1,0,0,1,0], X).
false.
?- count(1, Xs, X).
Xs = [],
X = 0 ;
Xs = [1],
X = 1 ;
Xs = [1, 1],
X = 2 ;
Xs = [1, 1, 1],
X = 3 ...
?- Xs = [_,_,_], count(1, Xs, X).
Xs = [1, 1, 1],
X = 3 ;
false.
首先我意识到查询根本不起作用,然后我概括查询。我用变量Xs
替换了大列表并说:Prolog,为我填空!而Prolog做到了这一点,并准确地向我们揭示了它将成功的案例。
实际上,它仅仅以1的列表成功。这很奇怪。您的定义太受限制 - 它正确计算列表中的1,其中只有1,但所有其他列表都被拒绝。 @coder向您展示了如何扩展您的定义。
这是另一个使用library(reif)
的人
SICStus | SWI。或者,请参阅tfilter/3
。
count(X, Xs, N) :-
tfilter(=(X), Xs, Ys),
length(Ys, N).
更符合其他定义风格的定义:
count(_, [], 0).
count(E, [X|Xs], N0) :-
if_(E = X, C = 1, C = 0),
count(E, Xs, N1),
N0 is N1+C.
现在有一些更普遍的用途:
四元素列表如何看起来有3倍1?
| ?- length(L, 4), count(1, L, 3).
L = [1,1,1,_A],
dif(1,_A)
; L = [1,1,_A,1],
dif(1,_A)
; L = [1,_A,1,1],
dif(1,_A)
; L = [_A,1,1,1],
dif(1,_A)
; false.
因此剩下的元素必须与1
不同。
这是Prolog为我们提供的优良通用性。
答案 1 :(得分:4)
问题是如@lurker所述,如果条件(或更好的统一)失败,则谓词将失败。你可以为此目的制作另一个子句,使用纯粹的iso / 2并在iso中定义:
count(_, [], 0).
count(Num, [H|T], X) :- dif(Num,H), count(Num, T, X).
count(Num, [H|T], X) :- Num = H, count(Num, T, X1), X is X1 + 1.
以上不是最有效的解决方案,因为它留下了许多选择点,但它是一种快速而正确的解决方案。
答案 2 :(得分:2)
您只需让谓词在统一Num = X
失败。基本上,就像你不接受与你所计算的唯一条款不同的条款。
我向你推荐这个简单的解决方案,它使用尾递归并在线性时间内扫描列表。尽管篇幅很长,但它非常高效和优雅,它利用了声明性编程技术和Prolog引擎的回溯。
count(C, L, R) :-
count(C, L, 0, R).
count(_, [], Acc, Acc).
count(C, [C|Xr], Acc, R) :-
IncAcc is Acc + 1,
count(C, Xr, IncAcc, R).
count(C, [X|Xr], Acc, R) :-
dif(X, C),
count(C, Xr, Acc, R).
count/3
是启动器谓词。它需要术语计数,列表并给你结果值。
第一个count/4
是递归的基本情况。
当列表的头部与您要查找的术语统一时,将执行第二个count/4
。
在回溯时达到第三个count/4
:如果该术语不匹配,则统一失败,您不需要递增累加器。
Acc
允许您扫描传播递归处理的部分结果的整个列表。最后你只需返回它。
答案 3 :(得分:0)
我自己解决了这个问题:
count(_, [], 0).
count(Num, [H|T], X) :- Num \= H, count(Num, T, X).
count(Num, [H|T], X) :- Num = H, count(Num, T, X1), X is X1 + 1.
答案 4 :(得分:0)
我决定将我的解决方案添加到此处的列表中。
这里的其他解决方案使用显式统一/失败统一,或使用库/其他功能,但是我的解决方案使用cuts和隐式统一。请注意,我的解决方案与Ilario的解决方案相似,但使用削减简化了此过程。
count(_, [], 0) :- !.
count(Value, [Value|Tail],Occurrences) :- !,
count(Value,Tail,TailOcc),
Occurrences is TailOcc+1.
count(Value, [_|Tail], Occurrences) :- count(Value,Tail,Occurrences).
将这样的问题等同于通过归纳,基础案例和证明步骤来证明如何减少问题的方法来解决这样的问题。
第1行(count(_, [], 0) :- !.
)处理“基本情况” 。
在处理列表时,必须查看每个元素,最简单的情况是零个元素([]
)。因此,我们希望元素为零的列表不包含要查找的Value
的实例。
请注意,我在最终代码中将Value
替换为_
-这是因为如果列表中没有值,我们不在乎要寻找的值!因此,为避免单例变量,我们在此取反。
此后,我还添加了!
(剪切)-因为对于我们不希望Prolog回溯和失败的出现次数,只有一个正确的值-因此我们告诉Prolog我们找到了正确的值通过添加此剪切。
第2行和第3行处理“归纳步骤” 。如果我们在列表中有一个或多个元素,则应该处理。在Prolog中,我们只能直接查看列表的开头,因此让我们一次查看一个元素。因此,我们有两种情况-列表开头的值是我们正在寻找的Value
,或者不是。
第2行(count(Value, [Value|Tail],Occurrences) :- !, count(Value,Tail,TailOcc), Occurrences is TailOcc+1.
)处理列表的开头和我们要查找的值是否匹配。因此,我们仅使用相同的变量名,以便Prolog统一它们。
在我们的解决方案中,切口被用作第一步(通过告诉Prolog不要尝试任何其他规则,这使每种情况互斥,并使我们的解决方案在最后调用时得到优化)。
然后,我们找出列表中其余部分(称为TailOcc
)中术语的实例。我们目前尚不知道列表中有多少个词,但我们知道它比列表中其余词多一个(因为我们有一个匹配项)。
一旦我们知道列表的其余部分中有多少个实例(称为Tail
),我们就可以采用该值并将其添加1
,然后将其作为最后一个值返回我们的计数函数(称为Occurences
)。
第3行(count(Value, [_|Tail], Occurrences) :- count(Value,Tail,Occurrences).
)处理列表的开头和我们要查找的值是否不匹配。
由于我们在第2行中使用了剪切,因此只有在第2行失败(即没有匹配项)的情况下,才可以尝试此行。
我们只需要获取列表其余部分(末尾)中的实例数,并返回相同的值即可,而无需对其进行编辑。