执行swi-prolog和其他列表元素的操作

时间:2011-09-24 08:03:21

标签: prolog

如何按顺序对列表中的每个元素执行操作?

基于这两个资源:

  1. http://www.swi-prolog.org/pldoc/doc/swi/library/lists.pl
  2. http://www.swi-prolog.org/pldoc/doc_for?object=foreach/2
  3. 我想我总能依赖:

    • foreach(member(X, [1,2]), write(X)).

    这是确定性的,我可以在我自己的谓词中包装成员/ 2谓词,并且仍然总是按顺序迭代吗?

1 个答案:

答案 0 :(得分:20)

是的,但你必须担心你的谓词失败。如果可以,则不会处理列表中的其余元素,因为它会生成连接而不是故障驱动的循环。

我会更热衷于使用maplist/2,因为我认为它比foreach/2更广泛使用,但我之前也没有看过这个选项。 :)

编辑:让我们讨论一下故障驱动循环的含义。

Prolog中有两种原始迭代方法:递归和失败驱动的循环。假设我想打印出列表中的每个项目。递归方法看起来像这样:

print_all([]).
print_all([X|Rest]) :- write(X), nl, print_all(Rest).

所以给定一个像[1,2,3]这样的列表,这将会像这样扩展:

print_all([1,2,3])
  write(1), nl, print_all([2,3])
    write(1), nl, write(2), nl, print_all([3])
      write(1), nl, write(2), nl, write(3), nl, print_all([])
        write(1), nl, write(2), nl, write(3), nl.

这是member/2通常实施的方式:

member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).

所以你可以看到递归方法非常简单和通用。

另一种简单但有点皱眉的方法是模拟未能使用回溯机制。这称为故障驱动循环,如下所示:

print_all(List) :- member(X, List), write(X), nl, fail.
print_all(_).

当您运行此版本的print_all/1时,会发生什么比简单扩展稍微复杂一点。

print_all([1,2,3])
  member([1,2,3], 1)
    write(1), nl
      fail
  retry member([1,2,3], 2)
    write(2), nl
      fail
  retry member([1,2,3], 3)
    write(3), nl
      fail
retry print_all(_)
  true

口头上,fail迫使Prolog备份到它所做的最后一个选择点并尝试使用下一个解决方案。好吧,write/1nl/0不会产生选择点,因为它们只有一个解决方案,但member/2 确实有多个解决方案 - 每个项目一个名单。所以Prolog从列表中取出每个项目并打印出来。最后,当member/2用完解决方案时,Prolog会备份到前一个选择点,这是print_all/1谓词的第二个主体,它始终成功。所以输出看起来一样。我认为现在人们通常不喜欢使用故障驱动的循环,但是我不能很好地理解这些论点以便有用地鹦鹉学舌。

可以帮助您查看正在发生的事情的一件事是使用trace谓词并逐步扩展两个版本,看看您是否能够理解这些差异。我的上述注释完全弥补了这个答案,可能不太清楚。

回顾我最初写的内容和你的实际问题:

  • foreach将是确定性的
  • member将始终按顺序迭代,因为列表的定义方式必须依次访问每个项目

此外,这些日子至少在S.O.你会得到很多人告诉你使用maplist及其类似的东西,所以它可能不仅仅是起作用,而且也是一个好主意。