强制变量重新分配(Prolog)

时间:2013-04-29 15:05:35

标签: prolog primes

作业是接受两个变量,一个介于0和10,000之间的数字,以及介于1和该数字之间的循环素数的数量。

我无法通过递归将变量传递回来(我认为回溯就是所谓的。)我得到正确的数字,我很确定我的概念已经失效,我遇到的问题是当我尝试重新分配变量(?!)

时会抛出错误

以下是代码:

circPrimeCompare(Below, NumCirc):-
    RealNum is 0,
    circPrimeCompare(1, Below, RealNum, []),
    print('R: '),
    print(RealNum),
    nl,
    (NumCirc =:= RealNum).

circPrimeCompare(_, 0, _, _).     
circPrimeCompare(N, 1, _, _):- prime(N), print('aaa').
circPrimeCompare(X, Below, RealNum, L):-
    ( prime(X), X<Below ->
        print(X),
        nl,
        numDigits(X, Y),
        rotate(X, Y, N2),
        (   prime(N2) 
            ->  RealNum2 is RealNum + 1 
            ;   RealNum2 is RealNum
            ),
        X2 is X + 1,
        (   not(circPrimeCompare(X2, Below, RealNum2, L)) 
            ->  RealNum = RealNum2, print(RealNum), nl 
            ;   RealNum = RealNum2, print(RealNum), nl
            ),
        print('RealNum2: '),
        print(RealNum),
        nl
    ;
    ( X<Below ->
        X2 is X + 1,
        RealNumPass is RealNum,
        (   not(circPrimeCompare(X2, Below, RealNumPass, L)) 
            ->  RealNum = RealNumPass, print(RealNum), nl 
            ;   RealNum = RealNumPass, print(RealNum), nl
            ),
        print('RealNum: '),
        print(RealNum),
        nl
        )
    ).

这是跟踪:

   Fail: (26) circPrimeCompare(10, 10, 4, []) ? creep
^  Exit: (25) not(user:circPrimeCompare(10, 10, 4, [])) ? creep
   Call: (25) 4=4 ? creep
   Exit: (25) 4=4 ? creep
...
   Exit: (24) circPrimeCompare(9, 10, 4, []) ? creep
^  Fail: (23) not(user:circPrimeCompare(9, 10, 4, [])) ? creep
   Call: (23) 4=4 ? creep
   Exit: (23) 4=4 ? creep
...
   Exit: (22) circPrimeCompare(8, 10, 4, []) ? creep
^  Fail: (21) not(user:circPrimeCompare(8, 10, 4, [])) ? creep
   **Call: (21) 3=4 ? creep
   Fail: (21) 3=4 ? creep**
   Redo: (21) numDigits(7, _G589) ? creep

粗体部分是扔我的东西。我真的不明白为什么这样做。是因为变量本质上只是一个用途吗?关于如何解决它的任何想法?

(是的,我知道这是非常非常糟糕的代码。在此任务之前,我从未在Prolog中写过任何内容。)

3 个答案:

答案 0 :(得分:3)

您没有以“Prolog”的方式思考问题。特别是试图“重新分配”变量不是Prolog方式。变量在满足目标和子目标的过程中受到约束,而不是重新分配值,我们经常安排代码携带“累加器”变量,只是在计算的最后阶段将其值传递给另一个“最终”变量

回溯和递归是不同的事情。当目标(或子目标)失败时,回溯发生,并且Prolog“引擎”试图以不同的方式满足该目标。如果我们用尽不同的规则等来满足目标,那么它就失败了。 (这可能会导致Prolog引擎回溯到之前的子目标并尝试以不同的方式满足 。)

递归是指谓词本身定义的时候,即如果调用谓词可以导致同一个谓词再次作为子目标被调用。

要在Prolog中执行许多任务,我们必须在递归而不是迭代方面考虑它们,这在过程语言中是很自然的。这就是为什么我们使用“累加器”变量,这些变量在不习惯的眼睛看来是谓词中额外的和可能不必要的参数。

然而,一旦你看到这个想法在行动,你就可以通过新方式应用这个概念获得一些满足感。

让我们将给定列表中的数字加起来作为模型问题。天真的方法是写:

sum(List,Sum).

其中List是一个数字列表,Sum应该是我们的输出,在列表中保留规定的值总和。

基本想法很自然,你考虑列表的HeadTail,如果列表不是(还)为空,你想要“重新分配”Sum = Sum + Head ,然后递归地处理Tail(直到列表为空,我们有Sum我们想要的)。

但这在Prolog中没有意义,因为你不能像过程语言允许的那样改变Sum的值。这是累加器参数被添加到我们的谓词并执行相同的工作的地方,但是以Prolog喜欢的方式。

sumAux([ ],Sum,Sum).
sumAux([H|T],Accum,Sum) :-
    NewAccum is Accum + H,
    sumAux(T,NewAccum,Sum).

sum(List,Sum) :- sumAux(List,0,Sum).

这里我们引入了一个带有额外参数的“辅助”谓词sumAux/3,我们通过将“累加器”中间参数设置为零来定义sum/2这个谓词,但是传递ListSum个参数。

关于它是如何工作的一点思考将会回报你的努力,很快我就会期待你将以各种新颖的方式应用这种范式(例如计算循环素数)。

答案 1 :(得分:1)

"backtracking"是概念的名称;这意味着如果事实证明这是不幸的话,那就回到自己的身边,违背一个人的决定。在Prolog程序中,我们有规则,显示哪些子目标必须被证明,主要目标(规则主管)被视为已被证明。当有几条规则时,我们可能会尝试第1条;如果后来证明它无法证明,我们回溯到最新的决策点,并尝试证明下一条规则(如果有的话)。

接下来,我们实例化我们的变量,给它们赋值。一旦为变量确定了一个值,它就无法改变,而我们仍在努力证明我们的目标。当我们说X=2时,我们承诺这个选择。我们现在不能说X=5,我们会成为骗子,而我们的“证明”将变得毫无价值。

Prolog的本质是,当我们向前迈向证明我们的目标,并做出关于什么是什么的各种决定时,我们将这些价值收集到什么是替换,变量和它们之间的映射价值观,这不是自相矛盾的;当我们达到最终目标时,我们的变量的这组值就是我们的答案。

但是当我们失败并且必须回溯时,我们迄今为止所做的所有实例都以相反的顺序撤消。

具体而言,NumCirc =:= RealNum不是作业。这是一个数字比较,可能是真或假。如果确实如此,我们会说目标NumCirc =:= RealNum 成功(已经过验证)。如果它是假的,我们说目标失败,因此我们回溯。

另一个关键概念是unification:当我们说X = Y时,我们声称它们都是相同的。当然4=4成功,3=4失败。

是的,Prolog本质上是set-once language(没有回溯,撤消分配,而不是更改它,使变量未分配< / em>再次)。

答案 2 :(得分:0)

正如已经指出的那样,常见的prolog习语是使用&#39; helper&#39;使用累加器播种的谓词来完成工作。此外,它有助于将问题分解为更小,更简单的部分。通常,你会拥有我称之为“公共谓词”的内容。除了强制执行约束并调用私人&#39;之外,它并没有做太多的事情。完成所有工作的辅助谓词。通常,辅助谓词与具有不同arity的公共谓词具有相同的名称(foo/3,而不是公共谓词foo/2。像这样:

%
% count the number of circular primes below the specified
% upper limit.
%    
count_circular_primes( Max , Cnt ) :-
  integer(Max) ,
  Max >=     1 ,
  Max =< 10000 ,
  count_circular_primes( Max , 0 , Cnt )
  .

这里的助手谓词count_circular_primes/3使用一个种子值为0的累加器值。它看起来像:

count_circular_primes( 0 , Cnt , Cnt ) .
count_circular_primes( N , T , Cnt ) :-
  N > 0 ,
  is_circular_prime(N) ,
  T1 is T + 1 ,
  N1 is N - 1 ,
  count_circular_primes(N1,T1,Cnt)
  .

您会注意到这是所有计数发生的地方。所有真正要做的就是确定一个特定的整数是否是一个圆形素数。简单!

您还会注意到,在整个递归操作完成之前,结果变量Cnt不会与任何内容统一。当N达到零时,累加器T与结果统一。

现在框架已经到位,让我们考虑如何确定单个整数是否是循环素数。这应该很简单:

  1. 号码是否为素数?
  2. 将其视为循环缓冲区并将数字1位置向左旋转
  3. 重复,直到你回到起点。
  4. 这是我如何进行列表轮换。回溯将产生整个列表轮换:

    rotate(Xs,Ys) :-
      append(A,[B|C],Xs) , % get the prefix (A) , suffix (C) and current item (B) from Xs
      append(C,A,D)      , % append the suffic (C) to the prefix (A) to get the leftmost bit (B)
      append([B],D,Ys)     % append the current item (B) to the leftmost bit (D) to get the rotation
      .
    

    以下是它的工作原理:append/3可用于为指定列表生成所有可能的前缀/后缀组合。如果我说append(Prefix,Suffix,[1,2,3,4])通过回溯返回的完整解决方案集是

    Prefix    Suffix
    ========= =========
    []        [1,2,3,4]
    [1]         [2,3,4]
    [1,2]         [3,4]
    [1,2,3]         [4]
    [1,2,3,4]        []
    

    一旦您可以生成轮换,findall/3可用于在给定特定输入的情况下将整个解决方案设置为列表。因此,鉴于上述rotate/2谓词,我可以说:

    findall( X , rotate( [1,2,3,4] , X ) , Rotations ).
    

    Rotations将与此列表列表统一:

    [ [1,2,3,4] , [2,3,4,1] , [3,4,1,2] , [4,1,2,3] ]
    

    一旦你有了一组旋转,你所要做的就是评估它是否代表素数。您可以使用forall/2执行此操作,其中包括:

    forall( member(Rotation,Rotations), is_prime(Rotation) )
    

    如果您不允许使用findall/3forall/2,事情会变得有点棘手,但我认为这会让您继续前进。