【问题标题】:Prolog - get the factors for a given number doesn't stop?Prolog - 获取给定数字的因子不会停止?
【发布时间】:2023-03-12 09:24:01
【问题描述】:

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

?- 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). 时,我得到了无限循环,屏幕上没有显示任何内容。

为什么?

问候

【问题讨论】:

  • 我不太了解 Prolog,但作为一个疯狂的猜测:您的程序是否可能除以 1 并递归调用自身?
  • @FrankSchmitt:除以 - 是的,但不是除以 1 ,而是除以 [1.....GivenNumber] 之间列出的每个元素,然后它才会递归调用.....这里有两个递归.
  • 您的范围似乎无法正常工作:
  • 您使用哪个 Prolog?你能追踪你的代码吗?
  • @joel76:SWI-Prolog 版本 6.2.5,作者 Jan Wielemaker (jan@swi-prolog.org)

标签: prolog failure-slice program-slicing


【解决方案1】:

您在问为什么查询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 次推理,即少于 234。这会敲响警钟吗?无论如何,您的程序存在问题。事实上,有两个相当独立的问题。我们怎样才能找到它们?

也许我们看错了一面。只需返回查询。我们的第一个错误是我们如何使用 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 特别有趣,因为它消除了整个子句:

?- 除数2(6,[])。 范围(I,I,[I]):- I = 6。 范围(I,K,[I|L]) :- K = 6, 我 K=6。 除数1([H|T],S,X):- false, 除数1(T,W,X), Z 是 X mod H, Z=0, S=[H|W]。 除数1([_|T],S,X):- S = [], X = 6, 除数1(T,S,X)。 除数2(X,结果):- X = 6,结果 = []。 范围(1,X,结果1), 除数1(结果1,结果,X)。

在其余部分的某个地方,有些东西太笼统了!其实divisors1/3的递归规则太笼统了。您修改后的这个新程序称为 slice,它是我们原始程序的专业化

解决这个问题的几种方法,最幼稚的方法是像这样添加相应的条件:

除数1([],[],_)。 除数1([H|T],S,X):- 除数1(T,W,X), 0 =:= X mod H, S=[H|W]。 除数1([H|T],S,X):- 除数1(T,S,X), 0 =\= X 模 H。

但是,程序的性能并没有提高。为了看到这一点,我将再次专门研究这个程序:

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

因此:无论 false 后面是什么,该程序都会尝试至少 3 * 2^N 推断长度为 N 的列表。

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

【讨论】:

    【解决方案2】:

    你这里有一个错误

    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。

    【讨论】:

    • 但是如何在 Prolog 中单行使用 if-then-else 呢?
    • (Z == 0 -> S = [H | W]; S = W)。括号很重要。
    • 非常感谢! +1 并被选中!
    • 现在,您可以改进您的代码:当您知道 Z 等于 0 时,您会得到 2 个除数 H 和 X div H。
    【解决方案3】:

    如果你更正你的范围(在递归结束子句中使用一个cut),你会得到它的工作。但是,您不会在找到所有除数后立即成功。

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

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

    请注意,此解决方案按升序返回除数。

    【讨论】:

    • 谢谢,但我想在没有内置插件(你知道 - HW)的情况下这样做,但 +1 寻求帮助。
    • @ron:如果我是你,我会尝试正确实现 bagof/3between/3。这并不难,并且有一些比我更擅长 Prolog 的人提供的解决方案 :)(或者至少足以编写关于该主题的教科书)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-17
    • 1970-01-01
    • 2014-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多