为什么这个基本的Prolog谓词不会停止执行?

时间:2014-03-15 11:44:40

标签: prolog primes primality-test

我想编写一个判断数字是否为素数的谓词。我是通过强力O(sqrt(n))算法来做到这一点:

1)如果number为2,则返回true,不再检查谓词。

2)如果数字是偶数,则返回false并不再检查谓词。

3)如果数字不均匀,请检查数字的除数,直至平方根。注意 因为如果我们到达这一部分,我们只需要检查从3开始的奇数除数 程序数量不均匀。 Evens在第2步被淘汰了。

4)如果我们找到偶数除数,则返回false并且不检查其他任何除数。

5)如果我们检查的除数​​大于数字的平方根, 返回true,我们发现没有除数。不再进行谓词检查。

这是我的代码:

oddp(N) :- M is N mod 2, M = 1.

evenp(N) :- not(oddp(N)).

prime(2) :- !.

prime(X) :- X < 2, write_ln('case 1'), false, !.

prime(X) :- evenp(X), write_ln('case 2'), false, !.

prime(X) :- not(evenp(X)), write_ln('calling helper'), 
prime_helper(X,3).

prime_helper(X, Divisor) :- K is X mod Divisor, K = 0, 
write_ln('case 3'), false, !.

prime_helper(X, Divisor) :- Divisor > sqrt(X),
write_ln('case 4'), !.

prime_helper(X, Divisor) :- write_ln('case 5'), 
Temp is Divisor + 2, prime_helper(X,Temp).

我遇到了问题。例如,如果我查询prime(1)。该程序仍在检查除数。我以为加了'!'会使程序停止检查先前条件是否为真。有人能告诉我为什么程序这样做吗?请记住,我是新手,我知道代码可以简化。但是,任何提示都将不胜感激!

2 个答案:

答案 0 :(得分:3)

您的计划有几个问题:

  1. 在致电!/0之后写一个剪辑false/0是没用的,因为永远不会达到剪辑。尝试交换这两个电话的顺序。
  2. 第一个条款可以简化为oddp(N) :- N mod 2 =:= 1.您也可以在其他条款中应用此简化。
  3. 谓词not/1最好被视为已弃用。改为写evenp(N) :- \+ oddp(N).(\+)/1是标准的操作符/控件结构,用于否定为失败。

答案 1 :(得分:3)

@Paulo引用了程序的关键问题导致它表现不正常以及一些好的提示。我将在此特定计划中添加一些提示。

  • 在编写谓词时,重点应该放在什么是真的。如果你的 谓词正确定义了成功的案例,那么你就不需要明确了 定义失败案例,因为它们默认情况下会失败。这意味着你的陈述#2和#4不需要特别定义为条款。

  • 您正在使用大量剪辑,这通常是您的计划的标志 没有有效或正确地定义。

在编写谓词时,首先以逻辑语言形式陈述目的是有帮助的(您在语句1到5中已经完成了,但我将在此处重述):

数字是素数,如果它是2(你的陈述#1),或者它是奇数并且它不能被奇数除数3或更高(你的陈述#3)整除 的。如果我们在Prolog中写出来,我们得到:

prime(X) :-                   % X is prime if...
    oddp(X),                  % X is odd, AND
    no_odd_divisors(X).       % X has no odd divisors
prime(2).                     % 2 is prime

如果X模块2评估为1,则数字X为奇数。

oddp(X) :- X mod 2 =:= 1.     % X is odd if X module 2 evaluates to 1

请注意,在我想要成功时,不是创建一个本质上失败的助手,而是要创建一个在我想要的时候成功的助手。如果X没有任何奇数除数&gt; = 3,no_odd_divisors将会成功。

数字X没有奇数除数,如果它不能被3整除,并且如果它不能被任何数字整除3 + 2k直到sqrt(X)(你的陈述#5) )。

no_odd_divisors(X) :-         % X has no odd divisors if...
    no_odd_divisors(X, 3).    % X has no odd divisors 3 or above

no_odd_divisors(X, D) :-      % X has no odd divisors D or above if...
    D > sqrt(X), !.           % D is greater than sqrt(X)
no_odd_divisors(X, D) :-      % X has no odd divisors D or above if...
    X mod D =\= 0,            % X is not divisible by D, AND
    D1 is D + 2,              % X has no odd divisors D+2 or above
    no_odd_divisors(X, D1).

请注意上面的一个。这表明当我们达到sqrt(X)以上时,我们做出了最终决定,我们不需要回溯到其他选项,因为没有奇数除数&#34; (对应于在语句#5中不再进行谓词检查。)。

这将产生以下行为:

| ?- prime(2).

yes
| ?- prime(3).

(1 ms) yes
| ?- prime(6).

(1 ms) no
| ?- prime(7).

yes
| ?-

请注意,我确实在上面定义了prime(2)子句。在这种情况下,prime(2)将首先失败prime(X) X = 2,然后成功prime(2)无处可回溯。如果我首先定义了prime(2),那么作为您的第一个语句(如果数字是2,则返回true并且不再检查任何谓词。)表示:

prime(2).                     % 2 is prime
prime(X) :-                   % X is prime if...
    oddp(X),                  % X is odd, AND
    no_odd_divisors(X).       % X has no odd divisors

然后你会看到:

| ?- prime(2).

true ? a

no
| ?-

这是完全有效的,因为Prolog首先在prime(2)成功,然后知道还有另一个条款要回溯,以寻找其他方法使prime(2)成功。然后它在第二次尝试失败并返回&#34; no&#34;。那&#34;不&#34;有时会混淆Prolog的新人。您也可以通过将子句定义为:{/ p>来阻止prime(2)情况下的回溯,无论子句顺序如何

prime(2) :- !.

您选择哪种方法最终取决于谓词关系的目的。使用剪切的危险在于您可能会无意中阻止您可能实际需要的替代解决方案。所以它应该非常周到地使用,而不是作为减少输出的快速补丁。