对 swi-prolog 等中的每个列表元素执行操作

发布于 2024-12-06 12:54:31 字数 521 浏览 0 评论 0原文

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

基于这两个资源:

  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 谓词包装在自己的谓词中,并且仍然始终按顺序迭代吗?

How do I do an operation for each element of a list, in order?

Based on these two resources:

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

I imagine I can always rely on:

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

Is that deterministic and can I wrap the member/2 predicate as I please in my own predicates and still always iterate in order?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

音盲 2024-12-13 12:54:31

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

我更愿意使用 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

从口头上来说,失败迫使 Prolog 备份到它所做的最后一个选择点,并尝试使用下一个解决方案。好吧,write/1nl/0 不会产生选择点,因为它们只有一个解决方案,但是 member/2 确实有多种解决方案——列表中的每一项都有一个解决方案。因此 Prolog 从列表中取出每个项目并打印它。最后,当member/2用完解决方案时,Prolog会备份到之前的选择点,即print_all/1谓词的第二个主体,它总是成功。所以输出看起来是一样的。我认为现在的人们通常不喜欢使用故障驱动循环,但我对这些论点的理解还不够深入,无法有效地重复它们。

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

回顾一下我最初写的内容和您的实际问题:

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

此外,至少现在,你会得到很多人告诉你使用 maplist 及其同类,所以它可能不仅可以工作,而且是一个好主意。

Yes, but you have to worry about your predicate failing. If it can, the remaining elements in the list will not be processed because it produces a conjunction rather than a failure-driven loop.

I would be more keen to use maplist/2 since I think it is more widely used than foreach/2 but I also hadn't seen this option before. :)

Edit: Let's discuss what I mean about failure-driven loops.

There are two primitive iteration methods in Prolog: recursion and failure-driven loops. Say I want to print out every item in a list. The recursive method is going to look like this:

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

So given a list like [1,2,3], this is going to expand like so:

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.

This is how member/2 is usually implemented:

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

So you can see the recursive method is pretty simple and general.

Another simple but somewhat frowned-upon method is to simulate a failure to engage the backtracking mechanism. This is called a failure-driven loop and looks like this:

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

When you run this version of print_all/1, what happens is a little more complex than simple expansion.

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

Verbally, the fail forces Prolog to back up to the last choice point it made and try using the next solution. Well, write/1 and nl/0 don't produce choice points because they only have one solution, but member/2 does have multiple solutions—one for each item in the list. So Prolog takes each item out of the list and prints it. Finally when member/2 runs out of solutions, Prolog backs up to the previous choice point, which is the second body of the print_all/1 predicate, which always succeeds. So the output looks the same. I think people nowadays generally prefer not to use failure-driven loops, but I don't understand the arguments well enough to parrot them usefully.

One thing that may help you to see what's going on is use the trace predicate and step through the expansion of both versions, and see if you can make sense of the differences. My notation above is completely made up for this answer and may not be as clear.

Looking back over what I originally wrote and your actual question:

  • foreach is going to be deterministic
  • member will always iterate in order, because lists are defined in such a way that you must access each item in turn

Moreover, these days at least on S.O. you will get a lot of people telling you to use maplist and its ilk, so it's probably not just going to work, but also a good idea.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文