【问题标题】:Why is this elementary Prolog predicate not stopping execution?为什么这个基本的 Prolog 谓词没有停止执行?
【发布时间】:2014-03-15 11:44:39
【问题描述】:

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

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

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

3) 如果数字不是偶数,检查数字的除数直到平方根。注意 我们只需要检查从 3 开始的奇数除数,因为如果我们到达这部分 该程序的数量不是偶数。在第 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)。该程序仍在检查除数。我以为添加“!”将使程序停止检查先前条件是否为真。有人能告诉我为什么程序会这样做吗?请记住,我是新手,我知道代码可以简化。但是,任何提示将不胜感激!

【问题讨论】:

    标签: prolog primes primality-test


    【解决方案1】:

    @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 没有任何奇数除数 >= 3,no_odd_divisors 将成功。

    如果一个数 X 不能被 3 整除,并且它不能被直到 sqrt(X) 的任何数 3+2k 整除(你的陈述 #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) 时,我们已经做出了最终决定,我们不需要回溯到“无奇数除数”的其他选项(对应于,不再进行谓词检查。 在你的陈述#5)。

    这将产生以下行为:

    | ?- prime(2).
    
    yes
    | ?- prime(3).
    
    (1 ms) yes
    | ?- prime(6).
    
    (1 ms) no
    | ?- prime(7).
    
    yes
    | ?-
    

    请注意,我确实在上面第二个定义了prime(2) 子句。在这种情况下,prime(2) 将首先以X = 2 失败prime(X),然后成功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) 成功。然后它在第二次尝试时失败并返回“否”。这个“不”有时会让 Prolog 新手感到困惑。无论子句顺序如何,您还可以通过将子句定义为:

    prime(2) :- !.
    

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

    【讨论】:

      【解决方案2】:

      你的程序有几个问题:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多