• Learn Prolog Now 翻译


    内容提要

     通过递归对列表进行遍历,从而完成各种操作。

     

     member/2这个谓词逻辑通过递归遍历了列表,对列表头部有一些操作,然后递归地对列表尾部做另外一些相同的操作。通过递归遍历列表在Prolog是十分普遍的做法,

    事实上,我们必须要掌握这项技能。所以我们学习如下的例子

     当我们使用列表的时候,我们经常会将一个列表和另一个列表进行对比,或者拷贝一个列表的内容到另一个列表去,或者翻译一个列表到内容到另一个列表去,或者

    类似到一些操作。这里有一个例子,假设我们有一个谓词a2b/2,有两个参数,第一个参数是a的列表,第二个参数是b的列表,而且两个列表的长度相同;比如,如果

    我们进行查询:

      ?- a2b([a, a, a, a], [b, b, b, b]).

     我们希望Prolog能够回答true。另外一方面,如果我们进行查询:

       ?- a2b([a, a, a, a], [b, b, b]).

     或者进行查询:

       ?- a2b([a, c, a, a],  [b, b, 5, 4]).

     我们希望Prolog能够回答false。

     当我们面对此类任务时,通常最好的解决问题的方式是首先从最简单的情况入手。现在,当使用列表时,思考最简单的情况通常意味着从空列表开始,而且在这个例

    子中确实也是有意义的。毕竟,什么样的列表是关于元素a最简单的列表?是空列表。为什么?因为它没有包含一个a元素。那么关于元素b最简单的列表呢?也是空列

    表。所以我们能够定义出最基础的信息如下:

       a2b([], []).

     这个明确的事实记录了关于a的空列表和关于b的空列表时相等的。虽然这个事实很明确,但是它将会在程序中发挥至关重要的作用,我们稍后就会看到。

     直到现在一切还好,那么如何进行下一步呢?这里有一个思路:对于更长的列表,通过递归去思考。所以,当谓词a2b/2去检查两个非空的列表,一个是关于a的,另

    一个是关于b的,如何确认两个列表有相同的长度呢?很简单:当第一个列表的头部是a,同时第二个列表的头部是b,并且a2b/2能够证明两个列表的尾部有相同的长度,

    我们就能够立刻写出如下的规则:

       a2b([a | Ta], [b | Tb]) :- a2b(Ta, Tb).

     解释一下:a2b/2能够成功的条件是,第一个参数是头部为a的列表,第二个参数是头部为b的列表,同时a2b/2能够在两个列表的尾部操作成功。

     这个定义有了很好的声明性。这是一个简单而又自然的递归谓词,基础子句处理空列表,递归子句处理非空列表。但是它实际是如何工作的?即,它的程序性含义是怎么样的?

    比如,如果我们查询:

      ?- a2b([a, a, a], [b, b, b]).

     Prolog将会回答true,也是我们期望的,但是这一切是如何发生的?

     让我们通过这个例子来学习。在例子中,两个列表都是非空的,所以事实子句不能提供帮助。所以Prolog就尝试使用递归规则,现在,查询能够满足这个规则(因为第一个列表

    头部是a,并且第二个列表的头部是b),所以Prolog有了新的目标,即:

      a2b([a, a], [b, b]).

     再一次地,事实子句不能提供帮助,但是递归规则能够再次被使用,导致接下来的目标是:

      a2b([a], [b]).

     事实子句还是不能提供帮助,但是递归规则可以,所以我们又有了如下的新目标:

      a2b([], []).

     最终我们可以使用事实了:它告诉我们true,我们确实有两个关于a和b的、长度相同的列表(空列表,什么都没有),这意味着如下的目标:

      a2b([a], [b]).

     也是成立的,这会导致目标:

      a2b([a, a], [b, b]).

     也是被满足的,所以原始的目标:

      a2b([a, a, a], [b, b, b]).

     是满足的。

     我们总结这个过程如下:Prolog从两个列表开始,通过检查第一个列表的头部是否为a,第二个列表的头部是否为b,然后去掉两个列表的头部;而后,使用相同的处理方式对两个

    列表的尾部进行操作。为什么这个过程会终止?因为每一次的递归后,列表都会变短,最终因为是空列表,所以会终止。从这个角度来说,程序中的事实会发挥决定性的作用:

    它给出true的回答,并且终止了递归,从而确保原始的查询是成功的。

     了解查询失败也是同样重要的。比如,如果我们查询:

      ?- a2b([a, a, a, a], [b, b, b]).

     Prolog会正确地回答false,为什么?因为经过了去掉头部-循环尾部的处理过程三次后,会剩下如下的目标:

      a2b([a], []).

     但是这个目标无法满足。如果我们查询:

      a2b([a, c, a, a], [b, b, 5, 4]).

     经过去掉头部-循环尾部的处理过程仅仅一次,Prolog会有如下的新目标:

      a2b([c, a, a], [b, 5, 4]).

     同样地,这个目标也无法满足。

     以上是a2b/2简单的使用情况,但是我们还没有完全覆盖完所有的使用场景。Prolog的使用过程中,查询输入变量始终是一个尝试的好方式。这时a2b/2会有一些有趣的事情发生,

    会表现得像一个转换器,将元素a的列表,转换为元素b的列表。比如查询:

      ?- a2b([a, a, a, a], X).

      X = [b, b, b, b]

     即,元素a的列表,已经被转换为元素b的列表。类似地,通过在第一个参数位置使用变量,我们可以将元素b的列表,转换为元素a的列表:

       ?- a2b(X, [b, b, b, b]).

      X = [a, a, a, a]

     你能够根据这个结果知道它是如何发生的吗?总结一下:a2b/2是一个很简单的、通过递归遍历列表的例子。但是不要被它的简单性迷惑:此类程序展示了Prolog的基础功能。

    无论是其声明形式(一个处理空列表的基础子句,一个处理非空列表的递归子句),还是具体执行的程序性(在列表头部做一些操作,然后对其尾部进行同样的递归处理),

    都会在Prolog编程中反复使用。事实上,在你的Prolog生涯中,你会发现你在写各式各样的a2b/2谓词,或者是其更复杂的变体,许多时候加入了很多的装饰,但是本质上是一样的。

  • 相关阅读:
    4.23Java自定义链表最终封装与完善
    4.23Java vector向量
    托管与非托管
    关于C#中的DLLImport (引)
    C#中通过DllImport使用 Win32 API(引)
    ASP.Net HttpHandler (转)
    C#WinForm获取本机网卡的型号,IP地址,子网掩码和网关
    ManagementObject
    asp.net Session详解(再转)
    C#优化字符串操作(引)
  • 原文地址:https://www.cnblogs.com/seaman-h-zhang/p/4639432.html
Copyright © 2020-2023  润新知