不使用递归的阶乘计算



我正在尝试实现一个不使用递归的阶乘计算(n!)的解决方案,仅使用prolog的逆操作。例如:

factorial(0, 1).
factorial(1, 1).
factorial(2, 2).
retroaction(X, Y) :-
factorial(K, Y),
not(K = X),
fail.
retroaction(K, Y) :- factorial(K, Y).   

对于固定事实factorial,如果我调用谓词retroaction来发现2的阶乘,例如,我将得到正确的答案:

?- retroaction(2, X).
X = 2.

我想实现的解决方案是:

factorial_ret(Number, _) :-
    retractall(factorial(_, _)),
    assertz(factorial(0,1)),
    factorial(X, Y),
    not(X = Number),
    NewNumber is X + 1,
    Factorial is Y * NewNumber,
    retractall(factorial(_, _)),
    assertz(factorial(NewNumber, Factorial)),
    fail.
factorial_ret(Number, Y) :- 
    factorial(Number, Y).

如果我试着求一个大于1的数的阶乘,行不通。有人知道为什么吗?

> 我的良心迫使我取消问你的问题。

对于来说,seek是一个禁忌,即使在命令式编程中也是如此,在Prolog中更是如此。

prolog-assert在某些情况下肯定会非常有用;我通常将其视为"最后的武器",而不是"首选武器"。在我看来,使用它的可能后果包括:

  • 声明式编程被抛弃了。
  • 逻辑纯度很容易丢失。
  • 的调试变得更加困难。
  • 代码复杂性上升。
  • 性能下降。
  • 闻起来像全局变量。

例子?看看@CapelliC和@Boris的回答吧。引用@Boris:

我写这个答案是因为它比@CapelliC的正确答案少了一些不必要的代码。

让我们运行一些查询并将该评估置于测试中!

?- factorialBoris(0,0).
**LOOPS**
?- factorialBoris(0,2).
**LOOPS**
?- factorialBoris(1,2).
**LOOPS**
?- factorialCapelliC(0,0).
**LOOPS**
?- factorialCapelliC(0,2).
**LOOPS**

нет!

请不要误会我的意思!我不想放下工作和努力的@CapelliC和@Boris, 两个Prolog顶级用户在SO,以任何方式,形状或形式。

底线:

在Prolog中使用命令式编程风格很容易适得其反!

使用prolog-assert严格测试代码比测试逻辑上纯粹的代码要困难得多,甚至可以很多,相信我!

在不确定的情况下,选择明智的道路:简单地做正确的事:)

尽可能保持逻辑的纯洁性——不要白白牺牲。

正如@lurker指出的那样,您混淆了'存储谓词'和'定义谓词'。你需要阶乘/2的定义,所以它对retractall(factorial(_,_))没有什么意义。

下面,我使用retract(mem(N, F))检索临时值,并为最终更新准备DB。应该总是只有一个mem/2的实例。

% replace recursion with a failure driven loop
:- dynamic mem/2.
factorial(X, Y) :-
    assert(mem(0, 1)),
    repeat,
    retract(mem(N, F)),
    (  X == N % done ?
    -> Y = F, % yes, succeed
       !      % but be sure to avoid 'external' backtracking...
    ;  N1 is N + 1,
       F1 is N1 * F,
       assert(mem(N1, F1)),
       fail   % loop: 'goto repeat'
    ).

请注意,repeat,...,fail之间的分支中的所有内置代码都是不可回溯的(好吧,除了retract/1)。

还请注意,这样的代码将比递归定义慢得多(并且不能在多线程的SWI-Prolog中工作)。

我认为断言/撤销早在Prolog程序员需要处理"知识库"时就可用了,而不是作为全局变量。一些prolog为全局变量提供了专门的库谓词,因为它们有合法的用途:例如,参见memoization。

PS: '追溯'是线性系统稳定性理论中使用的术语,我从未见过它用于编程

edit可以说明如何解决@repeat报告的错误:只需交换统一和切割。我们还需要检验X是一个正整数。真诚地,我认为这实际上与手头的问题无关。

...
    (  X == N % done ?
    -> !,     % yes, be sure to avoid further backtracking
       Y = F  % unify with the computed factorial
    ;  N1 is N + 1,
...

假设你所说的"retroaction"指的是"memoization",一种方法是:

只要你没有找到你需要的阶乘,找到到目前为止最大的一个,计算下一个,冲洗,重复。

我写这个答案是因为它比@CapelliC的正确答案少了一点不必要的代码。

你只需要一个开始的事实,0的阶乘:

然后您可以使用repeat循环来执行"只要"。然后,如果你总是在预先计算的阶乘堆栈的顶部添加新的阶乘,并且你总是只看一次,你可以确定你找到了迄今为止最大的阶乘:

:- dynamic factorial/2.
factorial(0, 1).
factorial_memoize(N, F) :-
    repeat,
        (   factorial(N, F0)
        ->  !, F = F0
        ;   once(factorial(N0, F0)),
            succ(N0, N1),
            F1 is N1 * F0,
            asserta(factorial(N1, F1)),
            fail
        ).

程序是这样工作的:

?- listing(factorial/2).
:- dynamic factorial/2.
factorial(0, 1).
true.
?- factorial_memoize(0, F).
F = 1.
?- listing(factorial/2).
:- dynamic factorial/2.
factorial(0, 1).
true.
?- factorial_memoize(5, F).
F = 120.
?- listing(factorial/2).
:- dynamic factorial/2.
factorial(5, 120).
factorial(4, 24).
factorial(3, 6).
factorial(2, 2).
factorial(1, 1).
factorial(0, 1).
true.

您的not子句中有小写的k

最新更新