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,忽略輸入的句子,
替換為合適的詞,我們能使用這個解析程式,來構建生成的程式。
實現阿麗莎的想法,顯示首先生成的句子。