【问题标题】:Problem with appending element using difference lists in Prolog在 Prolog 中使用差异列表附加元素的问题
【发布时间】:2021-06-06 13:09:03
【问题描述】:

我在this answer 中找到了这个 Prolog 代码,它使用差异列表实现了一个队列:

%% empty_queue(-Queue)
% make an empty queue
empty_queue(queue(0, Q, Q)).

%% queue_head(?Queue, ?Head, ?Queue0)
% Queue, with Head removed, is Queue0
queue_head(queue(s(X), [H|Q], Q0), H, queue(X, Q, Q0)).

%% queue_last(+Queue0, +Last, -Queue)
% Queue0, with Last at its back, is Queue
queue_last(queue(X, Q, [L|Q0]), L, queue(s(X), Q, Q0)).

这样做会按预期工作:

..., empty_queue(Q), queue_last(Q, 999, Q_), writeln(Q_), ....

我明白了

queue(s(0),[999|_3076],_3076)

如果我用这个 sn-p 观察 Q 的值,也很有趣:

empty_queue(Q), writeln(Q), queue_last(Q, 999, Q_), writeln(Q)

我明白了:

queue(0,_3750,_3750)
queue(0,[999|_3758],[999|_3758])

我想应该是这样的,因为差异导致空列表,所以它们有点等价。

问题是,在命令之后

queue_last(Q, 999, Q_)

我不能重用Q 来创建Q__,例如:

empty_queue(Q), queue_last(Q, 999, Q_), queue_last(Q, 888, Q__)

因为queue_last(queue(X, Q, [L|Q0]), L, queue(s(X), Q, Q0)).的绑定失败。

L = 888, L = 999 (tries to be both)

我该如何解决这个问题?有一些解决方法吗? (总是使用差异列表)

【问题讨论】:

  • 这是怎么回事? X 可以同时使用任何语言的12 吗?同一个列表可以同时是L=[1,2]L=[1,999] 吗?它可以同时是L=[1,2|T]L=[1,2,999|T2]。 (然后T 甚至可以是T=T2)。
  • 链接复制队列示例的答案:stackoverflow.com/a/31925828/14411997
  • 您应该仔细阅读该答案。 :) 我相信它已经讨论了其中的一些内容。

标签: prolog difference-lists


【解决方案1】:

我不能重用Q 来创建Q__

那是因为您必须使用您称之为Q_ 的“线程化”新结构。旧的Q 是燃烧器,必须丢弃。它不再正确描述“差异列表”。

?- empty_queue(Q1), 
   queue_last(Q1, 999, Q2), 
   queue_last(Q2, 888, Q3).

Q1 = queue(0,[999,888|_14714],[999,888|_14714]),   % Useless
Q2 = queue(s(0),[999,888|_14714],[888|_14714]),    % Burnt
Q3 = queue(s(s(0)),[999,888|_14714],_14714).       % Correct, valid

empty_queue(Q1) 调用之后,这是Q1

queue
├── arg 0: 0
├── arg 1: ----+---> <empty cell #1>
|              |
└── arg 2: ----+

queue_last(Q1, 999, Q2) 调用之后,这是Q1Q2

Q1(无效)

queue
├── arg 0: 0
├── arg 1: ----+---->[|]
|              |     / \
|              |  999  <empty cell #2>
|              |
└── arg 2: ----+

Q2(有效)

queue
├── arg 0: s(0)
├── arg 1: --------->[|]
|                    / \
|                 999  <empty cell #2>
|                          ^
|                          |
└── arg 2: ----------------+

queue_last(Q2, 888, Q3) 调用之后,这是Q1Q2Q3

Q1(无效)

queue
├── arg 0: 0
├── arg 1: ----+---->[|]
|              |     / \
|              |  999   [|]
|              |       /   \
└── arg 2: ----+    888    <empty cell #3>

Q2(无效)

queue
├── arg 0: s(0)
├── arg 1: --------->[|]
|                    / \
|                 999  [|]<------------------+
|                      /  \                  |
|                   888   <empty cell #3>    |
|                                            |
└── arg 2: ----------------------------------+

Q3(有效)

queue
├── arg 0: s(s(0))
├── arg 1: --------->[|]
|                    / \
|                 999  [|]
|                      /  \              
|                   888   <empty cell #3>
|                                ^
|                                | 
└── arg 2: ----------------------+

【讨论】:

  • 好的,我明白了,如果我想使用“烧毁”的一个两次以便从初始队列产生两个不同的队列,有什么解决方法吗?
  • @but-why 您可以使用copy_term/2 创建一个包含重命名(新)变量的深层副本,即新分配的“空单元格”。
  • 但是如果我要复制一个列表,为什么我不应该首先使用 append 呢?
  • @but-why “差异列表”的想法是不会复制存在于 t0 的结构,您只需“追加到末尾”,不断增长在它仍然未定义的地方的结构。但是,为了保持差异列表中名为 tip 和 fin 的变量的“差异列表性”,您必须切换到新的上下文。这通常通过尾递归调用来完成。在使用“队列”的代码中,这是模糊的,因为它看起来像面向对象或功能,但实际上并非如此,您不能使用“旧队列”。
  • @but-why 如果你使用append/2t0 处的结构将被完全定义(没有空单元格),你可以永远保留它,但它也会必须在每次追加时完全复制。如果您想追加到“差异列表”,同时仍将 same 差异列表保留在t0 以供其他用途(但为什么?),您确实必须先对其进行深度复制,然后你不妨直接使用append/2
【解决方案2】:

不能重新分配 Prolog 变量。你不能重复使用它们。我不知道调用变量“烧毁”是否有很大帮助,它们没有被烧毁,它们绑定到一个具体的值。

不要使用“write”和朋友,除非你正在做一些复杂的打印式调试。在顶层尝试一下,无论如何您都会打印出所有内容。以下是使用此队列实现的方法。请注意,我使用的是Q0Q1Q2 等,因为一旦有多个下划线,我就无法计算下划线。

Enqueueinga,然后在队列末尾b

?- empty_queue(Q0), queue_last(Q0, a, Q1), queue_last(Q1, b, Q2).
Q0 = queue(0, [a, b|_15096], [a, b|_15096]),
Q1 = queue(s(0), [a, b|_15096], [b|_15096]),
Q2 = queue(s(s(0)), [a, b|_15096], _15096).

排队 a,然后是 b,然后弹出您排队的第一个值(FIFO 顺序):

?- empty_queue(Q0), queue_last(Q0, a, Q1), queue_last(Q1, b, Q2), 
    queue_head(Q2, Popped, Q3).
Q0 = queue(0, [a, b|_17772], [a, b|_17772]),
Q1 = queue(s(0), [a, b|_17772], [b|_17772]),
Q2 = queue(s(s(0)), [a, b|_17772], _17772),
Popped = a,
Q3 = queue(s(0), [b|_17772], _17772).

在前面推两次,然后弹出(LIFO order):

?- empty_queue(Q0), queue_head(Q1, x, Q0), queue_head(Q2, y, Q1), 
    queue_head(Q2, Popped, Q3).
Q0 = queue(0, _21688, _21688),
Q1 = Q3, Q3 = queue(s(0), [x|_21688], _21688),
Q2 = queue(s(s(0)), [y, x|_21688], _21688),
Popped = y.

我在您的问题下方的评论中链接的答案(这里又是:https://stackoverflow.com/a/31925828/14411997)详细解释了它是如何工作的。它还具有其他相关 Q/A 等的链接。

【讨论】:

    猜你喜欢
    • 2015-02-05
    • 1970-01-01
    • 2013-02-08
    • 1970-01-01
    • 1970-01-01
    • 2013-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多