Prolog 编程 - 解决方案的途径
我在大学学习序言并面临一些问题。我已经发现的只是解决问题的方法。然而,我更感兴趣的是思考的方式,即如何得到这样的解决方案。
有人可以给我关于这个领域的建议吗?我非常感谢你的帮助。
我举了一个我正在处理的例子,并且在这里找到了 stackoverflow 上的解决方案,但我寻找的是他是如何做到这一点的,他如何找到答案:)
编写一个谓词 flatten(List,Flat) 来展平 a列表,例如 flatten([a,b,[c,d],[[1,2]],foo],X) 将给出 X=[a,b,c,d,1,2,foo]。
这是我在 stackoverflow 上找到的答案:
flatten(List, Flattened):-
flatten(List, [], Flattened).
flatten([], Flattened, Flattened).
flatten([Item|Tail], L, Flattened):-
flatten(Item, L1, Flattened),
flatten(Tail, L, L1).
flatten(Item, Flattened, [Item|Flattened]):-
\+ is_list(Item).
这个答案属于用户 gusbro 并由用户 Parhs 询问,我试图找到一种方法联系用户 gusbro 询问他如何得出这样的答案,但我不能。
非常感谢。
I am studying prolog at university and facing some problems. What I already found out is just solution to a problem. However, I'm more interested in the way to think, i.e. how to get such solution.
Can somebody give me an advise on this field. I would really appreciate your help.
I give an example I am coping with and also, found a solution on stackoverflow here, but what I looking for is how does he do that, how does he find the answer :)
Write a predicate flatten(List,Flat) that flatten a list, e.g. flatten([a,b,[c,d],[[1,2]],foo],X) will give X=[a,b,c,d,1,2,foo].
This is the answer I found on stackoverflow:
flatten(List, Flattened):-
flatten(List, [], Flattened).
flatten([], Flattened, Flattened).
flatten([Item|Tail], L, Flattened):-
flatten(Item, L1, Flattened),
flatten(Tail, L, L1).
flatten(Item, Flattened, [Item|Flattened]):-
\+ is_list(Item).
this answer belongs to user gusbro and asked by user Parhs, I have try to find a way to contact user gusbro to ask him how he can derive such answer but I cannot.
Thank you very much.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
嗯,我只能说,解决问题的方式很大程度上取决于问题本身。有一系列问题可以使用递归来解决,Prolog 非常适合来解决它们。
在此类问题中,可以通过将较大问题划分为两个或多个案例类来导出更大问题的解决方案。
在一类中,我们有“基本情况”,当输入无法进一步分为更小的情况时,我们提供问题的解决方案。
另一类是“递归情况”,我们将输入分成几部分,分别求解,然后“连接”结果以给出更大输入的解决方案。
在 flatten/2 的示例中,我们希望将一个项目列表作为输入,其中每个项目也可以是一个列表,结果应是一个包含输入中所有项目的列表。因此,我们将问题分成不同的情况。
我们将使用辅助参数来保存中间展平列表,这就是我们实现 flatten/3 的原因。
因此,我们的 flatten/2 谓词将仅使用空列表作为起始中间扁平列表来调用 flatten/3:
现在对于 flatten/3 谓词,我们有两个基本情况。第一个处理空列表。请注意,当输入为空列表时,我们无法进一步划分问题。在这种情况下,我们只将中间扁平列表作为我们的结果。
我们现在采取递归步骤。这涉及获取输入列表并将问题分为两个步骤。第一步是展平此输入列表的第一项。第二步是递归地展平其余部分:
好的,因此调用 flatten(Item, L1, Flattened) 展平第一个项目,但将未绑定变量 L1 作为中间列表传递。这只是一个技巧,以便在谓词返回时,变量 L1 仍然保持无界,并且 Flattened 将采用 [...|L1] 形式,其中 ... 是 Item 的扁平项。
下一步,调用 flatten(Tail, L, L1) 展平输入列表的其余部分,结果以 L1 为界。
我们的最后一个子句实际上是另一个基本情况,处理单个项目(不是列表)。因此我们有:
它检查 item 是否是列表,当它不是列表时,它将结果绑定为带有 head=Item 的列表,并将中间扁平列表绑定为 tail。
Well, all I can say is that the way to solve a problem depends largely on the problem itself. There is a set of problems which are amenable to solve using recursion, where Prolog is well suited to solve them.
In this kind of problems, one can derive a solution to a larger problem by dividing it in two or more case classes.
In one class we have the "base cases", where we provide a solution to the problem when the input cannot be further divided into smaller cases.
The other class is the "recursive cases", where we split the input into parts, solve them separately, and then "join" the results to give a solution to this larger input.
In the example for flatten/2 we want to take as input a list of items where each item may also be a list, and the result shall be a list containing all the items from the input. Therefore we split the problem in its cases.
We will use an auxiliary argument to hold the intermediate flattened list, and thats the reason why we implement flatten/3.
Our flatten/2 predicate will therefore just call flatten/3 using an empty list as a starting intermediate flattened list:
Now for the flatten/3 predicate, we have two base cases. The first one deals with an empty list. Note that we cannot further divide the problem when the input is an empty list. In this case we just take the intermediate flattened list as our result.
We now take the recursive step. This involves taking the input list and dividing the problem in two steps. The first step is to flatten the first item of this input list. The second step will be to recursively flatten the rest of it:
Ok, so the call to flatten(Item, L1, Flattened) flattens the first item but passes as intermediate list an unbound variable L1. This is just a trickery so that at the return of the predicate, the variable L1 still remain unbounded and Flattened will be of the form [...|L1] where ... are the flattened items of Item.
The next step, which calls flatten(Tail, L, L1) flattens the rest of the input list and the result is bounded with L1.
Our last clause is really another base case, the one that deals with single items (which are not lists). Therefore we have:
which checks whether item is a list and when it is not a list it binds the result as a list with head=Item and as tail the intermediate flattened list.
首先,我将向您展示我解决问题的方法,然后我有一些学习递归思考的资源。
这是我对“展平列表列表(列表......)”问题的解决方案。我对其进行了注释以显示我是如何到达那里的:
首先,让我们定义解决方案的公共接口。我们定义
flatten/2
。它的主体由对内部实现 flatten/3 的调用组成,它采用一个累加器,作为空列表作为种子。这很简单。
内部谓词
flatten/3
稍微复杂一点,但不是很复杂。首先,我们有边界条件:空列表。这标志着我们需要做的事情的结束,因此我们将累加器与结果统一起来:
下一个(也是唯一的)其他情况是非空列表。为此,我们检查列表的头部。我们这里的规则是它需要展平并附加到结果中。编程的一个好的规则是编写描述性代码,Prolog 本身就是一种描述性语言,而不是过程性语言:它描述了问题的解决方案并让推理引擎进行推理整理一下事情。
所以...让我们描述一下现在需要发生什么,并把重点放在压平列表头部的机制上:
这也很容易。
这就是整个解决方案的本质。我们将问题分为三部分:
让我们继续实现如何展平单个列表元素。这也很容易。这里有两种情况:列表项可能是一个列表,也可能是其他东西。
首先,列表元素可能是未绑定的变量。我们不希望出现不良行为,例如无界递归的发生,因此让我们通过禁止无界术语(暂时)来直接解决这个问题。如果元素已绑定,我们会尝试通过再次调用公共接口
flatten\2
来展平它(oooooooh...更多递归!)这完成了两件事
flatten/2
就会失败。flatten_head/2
的工作就完成了。代码如下:
最后,我们必须考虑的最后一种情况是列表元素不是列表的情况(未绑定的变量、原子或其他一些序言术语)。这些已经是“扁平的”...我们需要做的就是将它们包装为单个元素列表,以便调用者 (
flatten\3
) 获得其“返回值”的一致语义:这是完整的代码:
每个单独的步骤都很简单。识别各个部分并将它们编织在一起是很困难的(尽管有时,弄清楚如何停止递归可能不太明显)。
一些学习资源
Eric Roberts 的 递归思考(1986)可能是最好的(唯一?)专门关于开发递归观点 WRT 开发软件的书。有更新版本用 Java 进行递归思考,20 周年纪念版 (2006),虽然我还没看过。
当然,这两本书都可以从常见的地方获得:Powell's、Amazon 等。
< img src="https://i.sstatic.net/OCqtU.jpg" alt="递归思考">
您可能还想阅读 Douglas Hofstadtler 的经典著作 哥德尔,埃舍尔,巴赫:永恒的金辫有些人认为这是有史以来最好的书。 YMMV。
也可从常见嫌疑人处获得:
一本新书,虽然不是直接关于递归理论,但可能有用,尽管我已经没看过(它得到了很好的评价)是 Michael Corballis 的 递归思维:
人类语言、思想和文明的起源
First, I'll show you my approach to the problem, then I've got some resources for learning to think recursively.
Here's my solution to the problem "flatten a list of lists (of lists ...)". I've annotated it to show how I got there:
First, let's define the public interface to our solution. We define
flatten/2
. It's body consists of a call to the internal implementation flatten/3, which takes an accumulator, seeded as an empty list.That was easy.
The internal predicate
flatten/3
is a little more complex, but not very.First, we have the boundary condition: the empty list. That marks the end of what we need to do, so we unify the accumulator with the result:
The next (and only) other case is a non-empty list. For this, we examine the head of the list. Our rule here is that it needs to flattened and appended to the result. A good rule of programming is to write descriptive code, and Prolog is itself a descriptive, rather than procedural, language: one describes the solution to the problem and lets the inference engine sort things out.
So...let's describe what needs to happen now, and punt on the mechanics of flattening the head of the list:
That, too, was easy.
That's the essence of the entire solution, right there. We've broken our problem into 3 pieces:
Let's move on to the implementation of how to flatten a single list element. That's easy, too. We've got two cases, here: the list item might be a list, or it might be something else.
First, the list element might be an unbound variable. We don't want untowards behaviour, like unbounded recursion happening, so let's take care of that straightaway, by disallowing unbound terms (for now). If the element is bound, we try to flatten it by invoking our public interface,
flatten\2
again (oooooooh...more recursion!)This accomplishes two things
flatten/2
fails if handed something other than a list.flatten_head/2
is done.Here's the code:
Finally, the last case we have to consider is the case of list elements that aren't lists (unbound vars, atoms or some other prolog term). These are already "flat"...all we need to do is wrap them as a single element list so that the caller (
flatten\3
) gets consistent semantics for its "return value":Here's the complete code:
Each individual step is simple. It's identifying the pieces and weaving them together that's difficult (though sometimes, figuring out how to stop the recursion can be less than obvious).
Some Learning Resources
Eric Roberts' Thinking Recursively (1986) is probably the best (only?) book specifically on developing a recursive point-of-view WRT developing software. There is an updated version Thinking Recursively With Java, 20th Anniversary Edition (2006), though I've not seen it.
Both books, of course, are available from the Usual Places: Powell's, Amazon, etc.
You might also want to read Douglas Hofstadtler's classic Gödel, Escher, Bach: An Eternal Golden Braid Some consider it to be the best book ever written. YMMV.
Also available from the Usual Suspects:
A new book, though not directly about recursive theory, that might be useful, though I've not seen it (it's gotten good reviews) is Michael Corballis' The Recursive Mind:
The Origins of Human Language, Thought, and Civilization