【问题标题】:Prolog list accidentally reversingProlog列表意外反转
【发布时间】:2014-04-22 21:57:55
【问题描述】:

我在 Prolog 中编写代码时遇到问题,该代码接受一个列表,删除所有素数,然后返回列表的其余部分。我写了一个素数检查器谓词prime/1,它工作得很好,但是当我将我的程序应用于列表时,就像我在Prolog中尝试做的几乎所有事情一样,我会根据需要返回没有素数的列表,但顺序相反。

primemover(L, Lcomp):-
    primeremover(L, [], Lcomp).
primeremover([], A, A).
primeremover([H | T], A, X):-
    \+ prime(H),
    primeremover(T, [H | A], X).
primeremover([H | T], A, X):-
    prime(H),
    primeremover(T, A, X).

我可以通过查看我的代码来了解为什么列表会反转,但我就是找不到解决方法。如果我尝试反转将非素数移动到中间列表的递归案例的头部和尾部,它会工作并以正确的顺序出现,但是每个值都在它自己的嵌套列表中,这比出来更糟糕倒退。

有没有简单的方法来解决这个问题?

【问题讨论】:

标签: list prolog primes


【解决方案1】:

我认为这可以解决您的问题:

primeremover([], []).
primeremover([H | T], [H | Y]):-
    \+ prime(H),
    primeremover(T, Y).
primeremover([H | T], Y):-
    prime(H),
    primeremover(T, Y).

我不确定我是否遗漏了什么,但我相信您将其作为一种功能语言而不是逻辑语言来处理。此外,第三个论点似乎并没有真正为解决方案添加一些东西。可以在不丢失功能的情况下删除它。

我没有你的 prime 谓词,但我用它来测试:

main :- primeremover([1,2,3,4,5], A), write(A).

prime(X) :- X = 2; X = 3; X = 5.

我使用了 GNU Prolog (1.4.0)。

【讨论】:

  • 非常感谢,这对我很有帮助,我想我现在可以看到如何以类似的方式解决其他问题。
【解决方案2】:

你不需要调用辅助函数来做到这一点,你可以直接使用输入变量和返回来做到这一点

代码:

primeremover([], []).

primeremover([H | T], Y):-
    prime(H),
    !,
    primeremover(T, Y).

primeremover([H | T], [H | Y]):-
        primeremover(T, Y).

【讨论】:

    【解决方案3】:

    您在primeremover/3 谓词中使用第二个参数作为累加器,即作为堆栈工作的辅助参数,收集中间结果。这种(有用的)技术通常用于定义递归谓词以获得尾递归的好处。这种技术的一个典型例子是定义一个用于反转列表的谓词。朴素的定义不是尾递归的,因此需要与列表长度成比例的空间:

    reverse([], []).
    reverse([Head| Tail], Reversed) :-
        reverse(Tail, Reversed0),
        append(Reversed0, [Head], Reversed).
    

    请注意,第二个子句中对reverse/2 谓词的递归调用不是最后一次调用。因此,随后的append/3 谓词调用必须通过将其保存在堆栈中来暂停,直到递归reverse/2 谓词终止。每次递归调用,此堆栈都会增长一个元素。但如果递归调用是最后一次调用,则不需要此堆栈。尾递归定义可以使用累加器进行编码:

    reverse(List, Reversed) :-
        reverse(List, [], Reversed).
    
    reverse([], Reversed, Reversed).
    reverse([Head| Tail], List, Reversed) :-
        reverse(Tail, [Head| List], Reversed).
    

    但是,在您的具体情况下,正如 Erwin 和 Guillermo 解释的那样,没有必要使用累加器,因为您可以在遍历输入列表时构造输出列表。然而,他们建议的代码可以通过避免测试输入列表的当前头部是否是素数两次(在 Erwin 解决方案的情况下)以及通过使用避免切割(在 Guillermo 解决方案的情况下)来进行改进Prolog 的标准 if-then-else 控制结构:

    prime_remover([], []).
    prime_remover([Head| Tail], NonPrimes):-
        (   prime(Head) ->
            prime_remover(Tail, NonPrimes)
        ;   NonPrimes = [Head| NonPrimesTail),
            prime_remover(Tail, NonPrimesTail)
        ).
    

    请注意,此版本是(也)尾递归的。

    【讨论】:

      【解决方案4】:

      这里是您的代码的最小编辑以纠正问题。

      %%// primeremover( +L, -Lcomp) 
      primeremover(L, Lcomp):-             %// was:
          primeremover(L, [], Lcomp).
      primeremover([], A, A).
      primeremover([H | T], A, [H | X]):-  %// primeremover([H | T], A, X):-
          \+ prime(H),
          primeremover(T, A, X).           %//     primeremover(T, [H | A], X).
      primeremover([H | T], A, X):-
          prime(H),
          primeremover(T, A, X).
      

      我们不是 prepending - 在前面添加 - 元素到累加器,并从最深的调用返回其最终值,我们 append - 在末尾添加 - 元素到返回列表,并在最深的调用处将其最终的结束指针设置为[]。两者本质上都是迭代过程,并且都是由 Prolog 编译的,即具有恒定的控制堆栈空间使用。

      两种变体都是尾递归的,但新的变体是尾递归modulo cons。

      由于变量A 不再用作累加器,而是用作最终“指针”(列表的最后一个cons 单元格),因此习惯上将其称为Z .

      新代码演示了“差异列表” 技术:逻辑变量A 和X 形成一对,将列表的前缀从X 描述为A列表 X 与其尾部 A 之间的区别”。

      因此,我们在接口调用中显式地获取了最终值:

      primeremover(L, Lcomp):-        
          primeremover(L, E, Lcomp), E = [].
      

      通过直接调用primeremover/3 谓词,我们可以根据需要为E 使用任何值,而不仅仅是硬编码的[]。

      这实际上比通常的命令式“累加器”技术(通过 cons,前置)在 Prolog 中编码更自然,除非我们实际上需要以相反的顺序构建我们的结果。虽然,将元素附加到开放式列表的末尾可以被视为累积。

      【讨论】:

        猜你喜欢
        • 2013-10-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-15
        相关资源
        最近更新 更多