这是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
谓词的定义,都使用前者,而不是所谓的更有效的替代方案。这是为什么?使用差异列表有什么缺点,他们不能证明效率提升是合理的吗?
答案 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).
其中a
和b
都是非终结符。请注意,(',')//2
如何在DCG中读作“然后”,完全消除了对append/3
的需求,并且无需尽快理解列表差异。
编辑:我想用出版物Teaching Beginners Prolog — How to teach Prolog中的引用来支持我的论点:
第二个错误是在课程中过早引入差异。 很容易首先提出列表差异,然后才明确 条款语法,但初学者对语法规则更加熟悉。
这绝对不是不是评论中要求的实证研究。尽管如此,我认为,在几十年内向至少3个不同国家的大学学生教授Prolog的Prolog老师的出版经验应该在某种程度上加以考虑,即使我们忽略了这个事实。我们有一个具体的初学者,他已经在过去2天内提交了关于列表差异的 3个不同的问题。