Prolog编程 - 解决方案的路径

时间:2011-09-28 09:09:57

标签: prolog

我正在大学学习prolog并面临一些问题。我已经发现的只是解决问题的方法。但是,我对思考的方式更感兴趣,即如何获得这样的解决方案。

有人可以就这个领域给我一个建议。我将衷心感谢您的帮助。

我举一个例子我正在处理,并且在这里找到了stackoverflow的解决方案,但我在寻找的是他是如何做到的,他是如何找到答案的:)

编写一个展平列表的谓词展平(List,Flat),例如展平([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的方式,问他如何得出这样的答案,但我不能。

非常感谢。

2 个答案:

答案 0 :(得分:2)

嗯,我只能说解决问题的方法在很大程度上取决于问题本身。有一些问题可以使用recursion来解决,其中Prolog非常适合解决它们。

在这类问题中,可以通过将其划分为两个或更多个案例类来获得更大问题的解决方案。

在一个课程中,我们有“基本情况”,当输入无法进一步划分为较小的情况时,我们提供问题的解决方案。

另一个类是“递归情况”,我们将输入分成几部分,分别解决,然后“加入”结果,为这个更大的输入提供解决方案。

在flatten / 2的示例中,我们希望将每个项目也可以是列表的项目列表作为输入,结果应该是包含输入中所有项目的列表。因此,我们将问题分解为案例。 我们将使用辅助参数来保存中间展平列表,这就是我们实现flatten / 3的原因。

因此,我们的flatten / 2谓词将使用空列表作为起始中间扁平列表来调用flatten / 3:

flatten(List, Flattened):-
   flatten(List, [], Flattened).

现在对于flatten / 3谓词,我们有两个基本情况。第一个处理空列表。请注意,当输入为空列表时,我们无法进一步划分问题。在这种情况下,我们只需将中间扁平列表作为结果。

flatten([], Flattened, Flattened).

我们现在采取递归步骤。这涉及获取输入列表并分两步分解问题。第一步是展平此输入列表的第一项。第二步是递归地压平剩下的部分:

flatten([Item|Tail], L, Flattened):-
  flatten(Item, L1, Flattened),
  flatten(Tail, L, L1).

好的,所以对展平(项目,L1,展平)的调用会使第一个项目变平,但会将未绑定变量L1作为中间列表传递。这只是一个诡计,因此在谓词返回时,变量L1仍然是无界的,Flattened的形式为[... | L1],其中......是Item的扁平项。

下一步,调用展平(Tail,L,L1)会使输入列表的其余部分变平,结果与L1绑定。

我们的最后一个条款实际上是另一个基本案例,即处理单个项目(不是列表)的案例。因此我们有:

flatten(Item, Flattened, [Item|Flattened]):-
  \+ is_list(Item).

检查item是否为列表,当它不是列表时,它将结果绑定为head = Item的列表,并将其作为中间展平列表的尾部。

答案 1 :(得分:2)

首先,我会告诉你我解决问题的方法,然后我有一些资源可以学习递归思考。

这是我解决问题的方法“展平列表清单(列表......)”。我已经注释了它以显示我是如何到达那里的:

  • 首先,让我们定义解决方案的公共接口。我们定义flatten/2。它的主体包括对内部实现flatten / 3的调用,它接受一个累加器,播种为空列表。

    flatten ( X , R ) :-
      flatten ( X , [] , R ) ,
      .
    

    这很容易。

  • 内部谓词flatten/3稍微复杂一些,但不是很复杂。

    • 首先,我们有边界条件:空列表。这标志着我们需要做的事情的结束,因此我们将累加器与结果统一起来:

      flatten( [] , X , X ).
      
    • 下一个(也是唯一的)其他情况是非空列表。为此,我们检查列表的头部。我们的规则是它需要展平并附加到结果上。编程的一个好规则是编写描述性代码,而Prolog本身就是描述性,而不是过程,语言:一个描述问题的解决方案并让推理引擎解决问题。

      所以......让我们来描述现在需要发生什么,以及对列表头部扁平化的机制进行讨论:

      flatten( [X|Xs] , T , Y ) :-
        flatten_head(X,X1)     ,
        append( T,X1,T1)       ,
        flatten( Xs , T1 , Y )
        .
      

      那也很容易。

这就是整个解决方案的精髓所在。我们已将问题分解为3个部分:

  • 一个特例(空名单)
  • 正常情况(非空列表)
  • 如何处理列表中的每个元素(尚未定义)。

让我们继续讨论如何展平单个列表元素的实现。这也很简单。我们有两种情况,这里:列表项可能是一个列表,或者它可能是其他的。

  • 首先,list元素可能是未绑定的变量。我们不希望不受欢迎的行为,如无限的递归发生,所以让我们通过禁止不受限制的条款来暂时解决(暂时)。如果元素被绑定,我们会尝试通过再次调用我们的公共接口flatten\2来展平它(oooooooh ...更多递归!)

    这完成了两件事

    • 首先,它告诉我们是否有一个列表:flatten/2如果递给列表以外的其他内容则会失败。
    • 其次,当它成功时,flatten_head/2的工作就完成了。

    这是代码:

    flatten-head( X , Y   ) :-     
      nonvar(X) ,
      flatten( X , Y )
      .
    
  • 最后,我们必须考虑的最后一种情况是列表元素不是列表(未绑定的变量,原子或其他一些序言术语)的情况。这些已经“平坦”......我们需要做的就是将它们包装为单个元素列表,以便调用者(flatten\3)获得其“返回值”的一致语义:

    flatten-head( X , [X] ).
    

这是完整的代码:

flatten ( X , R ) :-
  flatten ( X , [] , R )
  .

flatten( [] , X , X ) .
flatten( [X|Xs] , T , Y ) :-
  flatten_head(X,X1)      ,
  append( T,X1,T1)        ,
  flatten( Xs , T1 , Y )
  .

flatten-head( X , Y   ) :-
  nonvar(X)             ,
  flatten( X , Y )
  .
flatten-head( X , [X] ) .

每个步骤都很简单。它识别碎片并将它们编织在一起是很困难的(虽然有时候,弄清楚如何停止递归可能不那么明显)。

一些学习资源

要理解递归,首先必须了解递归 -anonymous

Eric Roberts的Thinking Recursively(1986)可能是最好的(唯一的)书,专门开发了一个递归的观点WRT开发软件。有一个更新版本Thinking Recursively With Java, 20th Anniversary Edition(2006),虽然我没有看到它。

当然,这两本书都可以从常用地方获得:鲍威尔,亚马逊等。

Thinking Recursively Thinking Recursively (with Java!)

你可能还想阅读Douglas Hofstadtler的经典Gödel, Escher, Bach: An Eternal Golden Braid有人认为它是有史以来最好的书。 YMMV。

Gödel, Escher, Bach: An Eternal Golden Braid

也可从常见嫌疑人处获得:

一本新书,虽然不直接关于递归理论,但可能有用,虽然我没有看到它(它得到了很好的评论)是Michael Corballis'The Recursive Mind: The Origins of Human Language, Thought, and Civilization

enter image description here