1. 程式人生 > >4.3.2 非確定化的程式的例子

4.3.2 非確定化的程式的例子

4.3.2 非確定化的程式的例子
4.3.3部分描述了AMB的直譯器的實現。首先,然而,我們
給出一些它如何能被使用的例子。非確定性程式設計的優勢是
我們能表達搜尋如何執行的細節,在高層次的抽象上,
表達我們的程式。

*邏輯謎題
如下的謎題(從迪斯曼1968)是簡單的邏輯謎題的一大類的經典:

甲,乙,丙,丁,辛五個人住在僅包括五層樓的公寓中,他們住在不同的樓層。
甲不住頂層,乙不住底層,丙不住在頂層和底層。丁住得比乙高。
辛與丙在不相鄰的層。丙與乙在不相鄰的層。每個人住哪層?

通過列舉所有的可能性與新增給定的限制,以正常的方式,我們能
確定誰住在哪一層。

(define  (multiple-dwelling)
   (let ((baker (amb 1 2 3 4 5))
         (cooper (amb 1 2 3 4 5))
  (fletcher (amb 1 2 3 4 5))
  (miller (amb 1 2 3 4 5))
  (smith  (amb 1 2 3 4 5))
 )
       (require  (distinct? (list baker cooper fletcher miller smith)))
       (require  (not (= baker 5))) 
       (require  (not (= cooper 1)))
       (require  (not (= fletcher 5)))
       (require  (not (= fletcher 1)))
       (require  (>  miller cooper))
       (require (not (= (abs (- smith fletcher)) 1)))
       (require (not (= (abs (- fletcher  cooper)) 1)))
       (list (list 'baker baker)
             (list 'cooper cooper) 
      (list 'fletcher fletcher)
      (list 'miller miller)
      (list 'smith smith)         
       )
   )
)

解釋表示式(multiple-dwelling) 生成結果如下 

((baker 3) (cooper  2)  (fletcher 4)  (miller 5)  (smith 1))

儘管這個簡單的程式能工作,但是很慢。練習4.39和練習4.40
討論了一些可能的改進。

練習4.38
修改多人居住問題的程式,忽略辛與丙在不相鄰的層的限制條件。
對於這個修改後的謎題有多少種解?

練習4.39
在程式中,限制的順序能影響結果嗎?它能影響找到一個答案的時間嗎?
如果你認為它有問題,通過重排序限制,演示比之前更快的程式。
如果你認為它沒有問題,討論你的案例。

練習4.40
在多人居住問題,在樓層分配要求是每個人單獨一個樓層,之前與之後
的兩個情況下,分別有多少個分配的集合存在?生成所有的可能的樓層
分配,再回溯並剪樹枝是很低效的。例如,限制的大部分都依賴居民與
樓層的變數中的一兩個,在樓層被分給所有的人之前,可以強加這些條件。
寫並演示一個更有效率的非確定性程式,解決這個問題,基於僅生成那些
沒有被之前的限制排除的可能的情況。(提示:這需要Let表示式的巢狀)

練習4.41
寫一個普通的scheme程式解決這個多人居住問題。

練習4.42
解決如下的“說謊”謎題(從菲利普 1934):

五個學校女生,在一起考試。她們的家長認為,在結果上
顯示了不同的興趣,家長們因此同意,在關於考試結果上,
每個女生給自己的家長寫信報告考試結果,每個女生應該
寫一個真的宣告和一個假的宣告。從她們的信中得到如下的
相關資訊:

A:“我是唯一的第三,D是第二”
B:“我第一。 C是第三”
C:“我第三, B是第五”
D:“我第二, E是唯一的第四”
E:“我是第四,A是第一”
在事實上,這五個女生的名次是什麼?

練習4.43
使用amb直譯器來解決如下的謎題:

 

試圖寫一個程式為了讓它執行得更有效率(見練習4.40)
如果我們沒有告訴瑪麗的最後的名稱是磨利,確定有多少解?

練習4.44
練習2.42描述了八皇后問題,把皇后放在棋盤上,
沒有兩個皇后之間是相互攻擊的。寫一個非確定性
程式解決這個問題。


*解析自然語言
程式設計為接受自然語言,作為它的輸入,開始試圖解析輸入,
也就是以某些語法結構匹配輸入。例如,我們可能試圖認識
簡單的句子組成的文章,句子是由一個名詞加一個動詞。例如貓吃。
為了完成這樣的分析,我們必須能夠標識出內容中的單獨的詞。
我們以一些包括各類詞語的列表來開始:

(define nouns '(noun student professor cat class))
(define verbs '(verb studies lectures eats sleeps))
(define articles '(article the a))

我們也需要一個語法,也就是,描述語法元素如何由更簡單的
元素組合起來的規則的集合。一個非常簡單的語法可能表示為
一個句子總是由兩個部分組成,一個名詞短語,再接一個動詞。
名詞短語由一個冠詞再接一個名詞。用這個語法,句子“貓吃”
被解釋為如下:

(sentence  (noun-phrase  (article the)  (noun  cat)) 
          (verb eats))

我們能用簡單的程式生成這樣的解析,這個簡單的程式
由針對語法規則的單獨的程式組成。為了解析一個句子,
我們能標識它的兩個部分,返回這兩個部分的一個列表,
再加上一個標籤符號“sentence”:

(define (parse-sentence)
(list  'sentence 
        (parse-noun-phrase)
 (parse-word verbs)))

一個名詞短語,相似的,通過找到一個冠詞加上一個名詞而解析:

(define (parse-noun-phrase)
   (list 'noun-phrase
        (parse-word  articles)
        (parse-word  nouns)
   )
)

在最低的層次,為了重複的檢查,向下解析接下來的未解析的單詞
內容中需要的部分的單詞列表。為了實現這個任務,我們維護了一個
全域性變數,它是輸入沒有被解析的部分。每一次我們檢查一個詞,
我們要這個全域性變數必須非空,它開始以一個單詞。如果這樣,我們
從這個變數中移除單詞,返回這個詞。

(define (parse-word word-list)
   (require (not (null?  *unparsed*)))
   (require  (memq (car *unparsed*) (cdr word-list)))
   (let  ((found-word (car *unparsed*)))
       (set!  *unparsed*  (cdr *unparsed*)) 
       (list (car word-list) found-word)
   )
)

為了開始解析,我們做的所有的事是設定*unparsed*為整個輸入,
試圖解析一個句子,檢查沒有什麼留下:

(define *unparsed* '())
(define  (parse input) 
         (set!  *unparsed*  input) 
  (let  ((sent (parse-sentence)))
        (require (null? *unparsed*))
        sent))

我們現在能試解析器並且驗證它的工作對我們的最簡單的句子:

;;; Amb-Eval input:
(parse '(the cat eats))
;;; Startig a new problem
;;; Amb-Eval value:
(sentence (noun-phrase (article the)  (noun cat))  (verb eats))

在這裡,Amb直譯器是有用的,因為它在require輔助下,
表示要解析的約束是很方便的。當我們考慮更復雜的語法,這有選擇,
自動化的搜尋與回溯,真實地隱藏了單位如何被解構。

讓我們把介詞新增到我們的語法的列表中。

(define prepositions '(prep for to in by with))

並且定義一個介詞的短語(例如 為了貓)介詞接上名詞:

(define (parse-prepositional-phrase)
   (list 'prep-phrasee 
         (parse-word  prepositions) 
  (parse-noun-phrase))
)

現在我們能定義一個句子為名詞短語接上一個動詞短語,
動詞短語能成為一個動詞或者用一個介詞短語擴充套件的動詞短語:

(define (parse-sentence)
   (list 'sentence 
         (parse-noun-phrase)
  (parse-verb-phrase))
)

(define  (parse-verb-phrase)
     (define (maybe-extend verb-phrase)
        (amb verb-phrase
      (maybe-extend  (list 'verb-phrase
                            verb-phrase
       (parse-prepositional-phrase))))
     )
     (maybe-extend (parse-word verbs))
)

我們能修改名詞的短語定義,來允許這樣的句子“貓在教室中”
我們所用的是呼叫一個名詞短語,我們現在呼叫一個簡單的
名詞短語,一個名詞短語將是一個簡單的名詞短語,或者
得用介詞短語擴充套件的名詞短語。

(define (parse-simple-noun-phrase)
   (list 'simple-noun-phrase
        (parse-word  articles)
        (parse-word  nouns)
   )
)

(define  (parse-noun-phrase)
     (define (maybe-extend noun-phrase)
        (amb noun-phrase
      (maybe-extend  (list 'noun-phrase
                            noun-phrase
       (parse-prepositional-phrase))))
     )
     (maybe-extend (parse-simple-noun-phrase))
)

我們的新的語法器讓我們解析更復雜的句子。例如:

(parse  '(the student with the cat sleeps in the class))
生成
(sentence  (noun-phrase 
                 (simple-noun-phrase (article the)  (noun student))
   (prep-phrase (prep with) (simple-noun-phrase (article the) (noun cat)))
                 )
    (verb-phrase
       (verb sleeps)  (prep-phrase  (prep in) 
                                    (simple-noun-phrase 
         (article the)  (noun class)))
    ) 
)

注意的是,一個給定的輸入可能有多個合法的解析。在句子
“教授上課給學生帶著貓”,可能是教授帶著貓上課,也能是學生有貓。
一個非確定性的程式找到了這些可能性:

(parse '(the professor lectures to the student with the cat))
生成

(sentence (simple-noun-phrase (article the) (noun professor))
          (verb-phrase 
    (verb-phrase  (verb lectures)
                  (prep-phrase (prep to)
                 (simple-noun-phrase
                       (article the) (noun student))))
    (prep-phrase (prep with)
                 (simple-noun-phrase (article the)
   (noun cat))))
 )

讓直譯器重試可以得到如下的結果:
(sentence (simple-noun-phrase (article the) (noun professor))       
    (verb-phrase  (verb lectures)
                  (prep-phrase (prep to)
           (noun-phrase  (simple-noun-phrase
                           (article the) (noun student))
                                              (prep-phrase (prep with)
                                                    (simple-noun-phrase
          (article the) (noun cat)))))))


練習4.45
使用如上的語法器,如下的句子能被解析成五種意思。
“教授給學生上課在教室中帶著貓”,給出五種解析,並解釋
這些含義之間的差異。

練習4.46
在4.1部分和4.2部分中的直譯器沒有確定運算元
被解釋使用哪種順序。我們將看到amb直譯器
解釋它們從左到右。解釋為什麼我們的解釋程式
在運算元以其它的順序解釋時,無法工作。

練習4.47
羅斯建議,因為一個動詞短語是一個動詞或者是
一個動詞短語再加上一個介詞短語。以如下的方式
定義程式parse-verb-phrase 是更正常的了:

(define (parse-verb-phrase)
   (amb (parse-word verbs)
      (list 'verb-phrase
            (parse-verb-phrase)
     (parse-prepositional-phrase))
   )
)

它能正常地工作嗎?如果我們改變了amb中的
表示式的順序,程式的行為改變了嗎?

練習4.48
擴充套件如上的語法解析器,來處理更多的複雜的句子。
例如,你能副檔名詞短語和動詞短語來包括形容詞和副詞,
或者處理複合的句子。

練習4.49
阿麗莎對生成有趣的句子比解析它們更有興趣。她的理由
是通過簡單修改程式parse-word,忽略輸入的句子,
替換為合適的詞,我們能使用這個解析程式,來構建生成的程式。
實現阿麗莎的想法,顯示首先生成的句子。