在Prolog的追加操作中使用差异列表的缺点是什么?

时间:2017-01-12 09:52:41

标签: performance list recursion prolog

这是this question about difference lists的后续问题。

通常,追加/ 3或conc / 3的内容如下:

append([], L, L).
append([H1|T1], L2, [H1|L3]):-
    append(T1, L2, L3).

这种递归的缺点是,当将长列表作为第一个参数时,它不能有效运行,因为整个列表需要在实际附加开始之前进行处理。

为了提高效率,我们可以使用不同的列表,只需要变量实例化,不需要递归来附加列表。

append_d(A1-Z1, Z1-Z2, A1-Z2).

问题在于,无论我在哪里寻找append/3谓词的定义,都使用前者,而不是所谓的更有效的替代方案。这是为什么?使用差异列表有什么缺点,他们不能证明效率提升是合理的吗?

2 个答案:

答案 0 :(得分:4)

我认为唯一的缺点是#34;使用append的差异列表版本而不是经典的append/3是因为您可能没有开始的差异列表。换句话说,如果您有这样的列表:

[a,b,c]

而不是:

[a,b,c|Back]

...如果你想追加一些东西,你必须遍历它。

只要您要将列表附加到彼此,您确实会从一开始就使用差异列表。顺便说一句,在现代Prolog中,通常使用单独的列表参数和其余参数。以下是一些使用差异列表的SWI-Prolog库谓词:

这个清单绝对不是详尽无遗的。关键是这些谓词通过使用差异列表,允许您在固定时间内向列表的后面添加更多内容。

一旦你发现自己使用append/3在你正在编写的程序中附加列表,你应该考虑使用差异列表。至于缺点,只要你不需要附加列表,你需要携带一个你不能用于任何事情的额外论点:那么,只需一个列表就足够了。

此外,append/3,通常实现,可以做更多的事情,而不仅仅是追加列表。请尝试以下查询:

?- append(A, B, [1,2,3]). % split a list
?- append([1,2,3], Back, List). % make a difference list
?- append(Prefix, _, [a,b,c,d]). % list prefix
?- append(_, Suffix, [a,b,c,d]). % list suffix

等等。

良好使用差异列表的一个很好的例子是Prolog队列(参见this answer的底部,示例以简化形式被盗,来自" Prolog的工艺"由Richard O'基夫)

后记

虽然你可以定义一个"差异列表追加"谓词,通常是不必要的,因为它只增加了一个间接层。假设我正在编写一个带有二叉树的谓词,并为您提供按顺序遍历产生的差异列表(列表及其后面):

% tree_inorder_list(+Tree, -List, -Back)
tree_inorder_list(nil, List, List).
tree_inorder_list(t(X, L, R), List, Back) :-
    tree_inorder_list(L, List, [X|List0]),
    tree_inorder_list(R, List0, Back).

使用单独的谓词进行追加只会添加代码。

是的,这可以写成DCG而不是!看起来好多了:

tree_inorder(nil) --> [].
tree_inorder(t(X, L, R)) -->
    tree_inorder(L),
    [X],
    tree_inorder(R).

而不是查询:

?- tree_inorder_list(Tree, List, []).

您必须查询DCG版本:

?- phrase(tree_inorder(T), List).

您可以使用listing/1查看此DCG定义如何转换为Prolog谓词:

?- listing(tree_inorder//1).
tree_inorder(nil, A, A).
tree_inorder(t(D, A, E), B, G) :-
    tree_inorder(A, B, C),
    C=[D|F],
    tree_inorder(E, F, G).

true.

这与上面的差异列表定义相同(除了一个统一,C=[D|F],没有内联)。

由于存在差异列表phrase/3,您可以在差异列表模式中的多个树上使用tree_inorder//1,并在固定时间内连接列表:

?- list_to_search_tree([c,a,b], A),
   phrase(tree_inorder(A), List, Back),
   list_to_search_tree([z,y,z,z,x], B),
   phrase(tree_inorder(B), Back).
A = t(b, t(a, nil, nil), t(c, nil, nil)),
B = t(y, t(x, nil, nil), t(z, nil, nil)),
List = [a, b, c, x, y, z],
Back = [x, y, z].

(感谢@WillNess建议添加此内容)

答案 1 :(得分:3)

我实际上将这个问题重新表述为:

  

使用append/3而不是其他方法有什么缺点?

答案:频繁使用append/3通常表示您的数据结构存在问题,在这种情况下,您应退后一步并考虑使用DCG < EM>不是的

在使用DCG的情况下,重复使用append/3通常意味着二次开销这一事实将其改善为线性时间(总计所有操作)已经经常证明这一点。

此外,使用DCG通常会使您的代码更易于阅读,因为您需要跟踪更少的参数和更少的变量。

由于您现在已经发布了几个关于列表差异的相关问题,我建议您暂时忘记它们。在我看来,您用于学习Prolog的材料可能过于强调DCG,因此您很快就会遇到更难以解决的列表差异主题。

当试图使用append/3时,请始终考虑使用使用DCG 来描述列表!在我看来,他们在内部被编译为谓词,因为列表差异的原因现在不需要关注你。稍后您将更容易理解列表差异!

请注意,在使用DCG时,append/3不需要 ,因为非终结符所描述的列表的串联更自然地编写为例如: / p>

?- phrase((a,b), Ls).

其中ab都是非终结符。请注意,(',')//2如何在DCG中读作“然后”,完全消除了对append/3的需求,并且无需尽快理解列表差异。

编辑:我想用出版物Teaching Beginners Prolog — How to teach Prolog中的引用来支持我的论点:

  

第二个错误是在课程中过早引入差异。   很容易首先提出列表差异,然后才明确   条款语法,但初学者对语法规则更加熟悉

这绝对不是不是评论中要求的实证研究。尽管如此,我认为,在几十年内向至少3个不同国家的大学学生教授Prolog的Prolog老师的出版经验应该在某种程度上加以考虑,即使我们忽略了这个事实。我们有一个具体的初学者,他已经在过去2天内提交了关于列表差异的 3个不同的问题