Prolog - 得到给定数字的因素不会停止?

时间:2013-01-11 07:26:48

标签: prolog failure-slice program-slicing

我需要找到给定数字的因子,例如:

?- divisors2(40,R).
R = [40,20,10,8,5,4,2,1].

代码:

% get all the numbers between 1-X 
range(I,I,[I]).
range(I,K,[I|L]) :- I < K, I1 is I + 1, range(I1,K,L).
% calc the modulo of each element with the given number :
% any x%y=0 would be considered as part of the answer 
divisors1([],[],_).
divisors1([H|T],S,X):-divisors1(T,W,X),Z is X mod H,Z==0,S=[H|W].
divisors1([_|T],S,X):-divisors1(T,S,X).
divisors2(X,Result) :-range(1,X,Result1),divisors1(Result1,Result,X).

但是当我运行divisors2(40,RR).时,我得到无限循环,屏幕上没有任何内容。

为什么?

此致

3 个答案:

答案 0 :(得分:6)

您在问为什么要为查询divisors2(40,R)获得无限循环。我几乎想用向你解释一下。唉......

......答案是:不,你没有得到无限循环!而你的程序也找到了答案。这是

R = [1, 2, 4, 5, 8, 10, 20, 40]

对我来说看起来很合理。它们按升序排列,你想要一个下降列表,但除此之外,这是一个完美的答案。不开玩笑。但是,我怀疑你没有足够的耐心来得到答案。对于36我需要:

?- time(divisors2(36,R)).
% 10,744,901,605 inferences, 2248.800 CPU in 2252.918 seconds (100% CPU, 4778061 Lips)
R = [1, 2, 3, 4, 6, 9, 12, 18, 36]

非常不寻常......对于最多36个微小整数的列表,Prolog需要10 744 901 605个推论,即小于2 34 。这会响铃吗?无论如何,您的程序都存在问题。事实上,有两个相当独立的问题。我们怎样才能找到它们?

也许我们正在寻找错误的一面。回到查询。我们的第一个错误是我们如何使用Prolog的顶层。得到答案给我们留下了非常深刻的印象。但Prolog为我们提供了进一步的答案!事实上:

?- time(divisors2(36,R)).
% 10,744,901,605 inferences, 2248.800 CPU in 2252.918 seconds (100% CPU, 4778061 Lips)
R = [1, 2, 3, 4, 6, 9, 12, 18, 36] ;
% 10 inferences, 0.000 CPU in 0.000 seconds (82% CPU, 455892 Lips)
R = [1, 2, 3, 4, 6, 9, 12, 18] ;
% 917,508 inferences, 0.192 CPU in 0.192 seconds (100% CPU, 4789425 Lips)
R = [1, 2, 3, 4, 6, 9, 12, 36] ...

这太繁琐了。也许一个小例子就足够了?

?- divisors2(6,R).
R = [1, 2, 3, 6] ;
R = [1, 2, 3] ;
R = [1, 2, 6] ;
R = [1, 2] ;
R = [1, 3, 6] ;
R = [1, 3] ;
R = [1, 6] ;
R = [1] ;
R = [2, 3, 6] ;
R = [2, 3] ;
R = [2, 6] ;
R = [2] ;
R = [3, 6] ;
R = [3] ;
R = [6] ;
R = [] ;
false.

绰绰有余!也许我们坚持使用最小的例子[]并重申它:

?- divisors2(6,[]).
true ;
false.

显然,这不是我们的预期。我们希望这会失败。如何本地化问题? Prolog中有一个通用的调试策略:

  

如果目标过于笼统,请对程序进行专门化。

我们可以通过添加更多目标来专门化该计划,以使上述查询仍然成功。我将添加 false 和一些 (=)/2 目标。 false 特别有趣,因为它消除了整个条款:

?- divisors2(6,[]).

range(I,I,[I]) :- I = 6.
range(I,K,[I|L]) :- K = 6,
   I < K,
   I1 is I + 1,
   range(I1,K,L).

divisors1([],[],X) :- K=6.
divisors1([H|T],S,X):- false,
   divisors1(T,W,X),
   Z is X mod H,
   Z=0,
   S=[H|W].
divisors1([_|T],S,X):- S = [], X = 6,
   divisors1(T,S,X).

divisors2(X,Result) :- X = 6, Result = [].
   range(1,X,Result1),
   divisors1(Result1,Result,X).

剩下的部分某处太笼统了!实际上divisors1/3的递归规则太笼统了。您的这个新修改程序称为 slice ,它是我们原始程序的 specialized

解决这个问题的几种方法,最天真的方法是添加相应的条件:

divisors1([],[],_).
divisors1([H|T],S,X):-
   divisors1(T,W,X),
   0 =:= X mod H,
   S=[H|W].
divisors1([H|T],S,X):-
   divisors1(T,S,X),
   0 =\= X mod H.

然而,该计划的表现并未改善。为了看到这一点,我将再次专攻这个计划:

divisors1([],[],_) :- false.
divisors1([H|T],S,X):-
   divisors1(T,W,X), false,
   0 =:= X mod H,
   S=[H|W].
divisors1([H|T],S,X):-
   divisors1(T,S,X), false,
   0 =\= X mod H.

因此:无论 false 背后有什么内容,此程序都会尝试至少3 * 2^N个推断,查找长度为N的列表。

通过将递归目标放在最后,我们可以避免这种情况。

答案 1 :(得分:4)

你有错误

divisors1([H|T],S,X):-
   divisors1(T,W,X),
   Z is X mod H,
   Z==0,S=[H|W]. <=== here

如果Z为零,则S = [H | W],否则S = W。

答案 2 :(得分:3)

如果你纠正你的范围(使用剪切作为递归结束条款),你会得到它的工作。但是你找不到所有的除数就不会立即成功。

使用您的一般想法的解决方案,以及/ 3和bagof / 3之间的内置插件(使打字更容易):

divisors(X, Divs) :- bagof(D, divs(X,D), Divs).
divs(X,D) :- between(1,X,D), 0 is X mod D.

请注意,此解决方案会按递增顺序返回除数。