【问题标题】:executing operation for each list element in swi-prolog and others对 swi-prolog 和其他中的每个列表元素执行操作
【发布时间】:2014-01-26 06:07:19
【问题描述】:

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

基于这两个资源:

  1. http://www.swi-prolog.org/pldoc/doc/swi/library/lists.pl
  2. http://www.swi-prolog.org/pldoc/doc_for?object=foreach/2

我想我总是可以依靠:

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

这是确定性的吗?我可以按照自己的意愿将 member/2 谓词包装在自己的谓词中,并且始终按顺序迭代吗?

【问题讨论】:

    标签: prolog


    【解决方案1】:

    是的,但你必须担心你的谓词失败。如果可以,列表中的其余元素将不会被处理,因为它会产生一个合取而不是一个失败驱动的循环。

    我会更热衷于使用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 和它的同类,所以它可能不仅会起作用,而且也是一个好主意。

    【讨论】:

    • 我看到了,所以建立一个列表,其中包含我想要所有解决方案的谓词的故障驱动循环,然后在循环上映射列表以打印它们。我认为在某处需要一个故障驱动循环因为我的程序非常基于证明而不是基于计算。
    • 无论哪种方式都应该总是可以做到的。如果可以,我建议避免使用故障驱动循环,但如果它更有意义,请使用它。
    • 我对 maplist/2 文档的阅读是该列表可以重新排序,这意味着操作将以任意顺序执行。这意味着它不能解决问题。递归增加了不描述我的意图的复杂性,但我认为 foldl/4 以一种合理的描述方式实现了我需要的东西,如果我只是在 apply 周围提供一个接受两个额外参数以不累积任何内容的 shim:act(A, _, _) : 调用(A)
    • @codeshot 我认为动作不能以任意顺序执行。毕竟,列表必须从头到尾构建和遍历——这就是链表的工作方式。您在文档中的什么地方看到了这一点?还有,五年后……?
    • 疯了吗?这是一种非常粗鲁的方式来接近仔细检查他们的工作的人。
    猜你喜欢
    • 1970-01-01
    • 2011-10-30
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多