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謂詞,或者是其更復雜的變體,許多時候加入了很多的裝飾,但是本質上是一樣的。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com