# 4.3.2非确定性程序的示例

> 译者： [https://sicp.comp.nus.edu.sg/chapters/87](https://sicp.comp.nus.edu.sg/chapters/87)

[部分4.3.3](88) 描述了`amb`评估程序的实现。 但是，首先，我们给出一些如何使用它的示例。 非确定性编程的优点是我们可以抑制搜索的执行细节，从而以更高的抽象级别表达程序。

## 逻辑难题

以下难题（摘自 &lt;citation&gt;Dinesman 1968&lt;/citation&gt; ）是一大类简单逻辑难题的典型代表：

> 贝克，库珀，弗莱彻，米勒和史密斯住在公寓的不同楼层，公寓只有五层。 贝克不住在顶层。 库珀不住在底层。 Fletcher不在顶层或底层。 米勒比库珀住在更高的楼层。 史密斯不在弗莱彻（Fletcher's）的地板上居住。 弗莱彻（Fletcher）不在库珀（Cooper）的地板上居住。 每个人都住在哪里？

We can determine who lives on each floor in a straightforward way by enumerating all the possibilities and imposing the given restrictions:[[1]](87#footnote-1)

```js
function multiple_dwelling() {
    const baker = amb(1, 2, 3, 4, 5);
    const cooper = amb(1, 2, 3, 4, 5);
    const fletcher = amb(1, 2, 3, 4, 5);
    const miller = amb(1, 2, 3, 4, 5);
    const smith = amb(1, 2, 3, 4, 5);
    require(distinct(list(baker, cooper, fletcher, miller, smith)));
    require(! baker === 5);
    require(! cooper === 1);
    require(! fletcher === 5);
    require(! fletcher === 1);
    require(! miller > cooper);
    require(! math_abs(smith - fletcher) === 1);
    require(! math_abs(fletcher - cooper) === 1);
    return list(list("baker", baker),
                list("cooper", cooper),
                list("fletcher", fletcher),
                list("miller", miller),
                list("smith", smith));
}
```

计算表达式`multiple_dwelling()`会产生结果

```js
[["baker", [3, null]], 
 [["cooper", [2, null]],
  [["fletcher", [4, null]],
   [["miller", [5, null]],
    [["smith", [1, null]], null]]]]]
```

尽管此简单功能有效，但速度非常慢。 练习 [4.30](87#ex_4.30)[4.31](87#ex_4.31) 讨论了一些可能的改进。

<exercise>Modify the multiple-dwelling function to omit the requirement that Smith and Fletcher do not live on adjacent floors. How many solutions are there to this modified puzzle?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Does the order of the restrictions in the multiple-dwelling function affect the answer? Does it affect the time to find an answer? If you think it matters, demonstrate a faster program obtained from the given one by reordering the restrictions. If you think it does not matter, argue your case.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>In the multiple dwelling problem, how many sets of assignments are there of people to floors, both before and after the requirement that floor assignments be distinct? It is very inefficient to generate all possible assignments of people to floors and then leave it to backtracking to eliminate them. For example, most of the restrictions depend on only one or two of the person-floor variables, and can thus be imposed before floors have been selected for all the people. Write and demonstrate a much more efficient nondeterministic function that solves this problem based upon generating only those possibilities that are not already ruled out by previous restrictions.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Write an ordinary JavaScript program to solve the multiple dwelling puzzle.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Solve the following <quote>Liars</quote> puzzle (from <citation>Phillips 1934</citation>):

> 五名女学生参加考试。 他们认为，他们的父母对结果表现出过高的兴趣。 因此，他们同意，在写有关考试的书信中，每个女孩都应发表一份真实的陈述，并发表一份虚假的陈述。 以下是他们信件中的相关段落：
> 
> *   贝蒂：&lt;quote&gt;凯蒂在考试中排名第二。我只有第三名。&lt;/quote&gt;
> *   Ethel：&lt;quote&gt;您会很高兴听到我在榜首。琼排名第二。&lt;/quote&gt;
> *   琼：&lt;quote&gt;我排名第三，可怜的老埃塞尔塞尔名列前茅。&lt;/quote&gt;
> *   凯蒂：&lt;quote&gt;我排名第二。玛丽只是第四。&lt;/quote&gt;
> *   玛丽：&lt;quote&gt;我排名第四。最高的是贝蒂（Betty）。&lt;/quote&gt;
> 
> 实际上，五个女孩的排列顺序是什么？

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Use the `amb` evaluator to solve the following puzzle:[[2]](87#footnote-2)

> 玛丽·安·摩尔（Mary Ann Moore）的父亲有游艇，他的四个朋友也都有：唐宁上校，霍尔先生，藤壶爵士和帕克博士。 五个人中的每个人都有一个女儿，每个人都以另外一个女儿的名字命名自己的游艇。 Barnacle爵士的游艇是Gabrielle，Moore先生拥有Lorna。 罗莎琳德·霍尔先生。 唐宁上校拥有的梅利莎（Melissa）以Barnacle爵士的女儿命名。 加布里埃尔的父亲拥有以帕克博士的女儿命名的游艇。 洛娜的父亲是谁？

Try to write the program so that it runs efficiently (see exercise <ref name="ex:better-multiple-dwelling2">[4.31](87#ex_4.31)</ref>). Also determine how many solutions there are if we are not told that Mary Ann's last name is Moore.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Exercise <ref name="ex:8queens">[2.42](32#ex_2.42)</ref> described the <quote>eight-queens puzzle</quote> of placing queens on a chessboard so that no two attack each other. Write a nondeterministic program to solve this puzzle.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

## 解析自然语言

设计为接受自然语言作为输入的程序通常从尝试_解析_输入开始，也就是尝试将输入与某种语法结构进行匹配。 例如，我们可能会尝试识别由句子，名词和动词组成的简单句子，例如&lt;quote&gt;猫吃的东西。&lt;/quote&gt; 要完成这样的分析，我们必须能够识别单个单词的词性。 我们可以从一些分类单词的列表开始： [[3]](87#footnote-3)

```js
const nouns = list("noun", "student", "professor", "cat", "class");

const verbs = list("verb", "studies", "lectures", "eats", "sleeps");

const articles = list("article", "the", "a");
```

我们还需要_语法_，即一组描述语法元素如何由较简单的元素组成的规则。 一个非常简单的语法可能会规定一个句子通常由两部分组成-名词短语后跟动词-并且名词短语由后跟名词的文章组成。 通过此语法，猫吃掉的&lt;quote&gt;句子&lt;/quote&gt;解析如下：

```js
["sentence", [["noun-phrase", [["article", ["the", null]], 
                               ["noun", ["cat", null]]]],
              ["verb", ["eats", null]]]]
```

我们可以使用一个简单的程序来生成这样的语法分析，该程序对每个语法规则都具有单独的功能。 为了解析一个句子，我们确定其两个组成部分，并返回这两个元素的列表，并用符号`sentence`标记：

```js
function parse_sentence() {      
    return list("sentence",
                parse_noun_phrase(),
                parse_word(verbs));
}
```

类似地，名词短语也可以通过找到紧随其后的名词的文章来解析：

```js
function parse_noun_phrase() {
    return list("noun-phrase",
                parse_word(articles),
                parse_word(nouns));
}
```

在最低级别上，解析归结为反复检查下一个未解析的单词是否是语音所需部分的单词列表的成员。 为了实现这一点，我们维护一个全局变量`unparsed`，它是尚未解析的输入。 每次检查单词时，我们都要求`unparsed`必须为非空，并且必须以指定列表中的单词开头。 如果是这样，我们将从`unparsed`中删除该单词，并将其连同其词性一起返回（在列表的顶部）： [[4]](87#footnote-4)

```js
function parse_word(word_list) {      
    require(! is_null(unparsed));
    require(member(head(unparsed), tail(word_list)) !== null);
    const found_word = head(unparsed);
    unparsed = tail(unparsed);
    return list(head(word_list), found_word);
}
```

要开始解析，我们需要做的就是将`unparsed`设置为整个输入，尝试解析一个句子，然后检查是否剩下任何东西：

```js
let unparsed = null;

function parse_input(input) {
    unparsed = input;
    const sent = parse_sentence();
    require(is_null(unparsed));
    return sent;
}
```

现在，我们可以尝试解析器，并验证它是否适用于我们的简单测试语句：

```js
// Amb-Eval input:      
parse_input(list("the",  "cat",  "eats"));
// Starting a new problem
// Amb-Eval value:
["sentence", [["noun-phrase", [["article", ["the", null]], 
                               ["noun", ["cat", null]]]],
              ["verb", ["eats", null]]]]
```

`amb`评估器在这里很有用，因为借助`require`可以方便地表达解析约束。 但是，当我们考虑更复杂的语法时，可以选择如何分解单位的自动搜索和回溯确实会有所收获。

让我们在语法中添加一个介词列表：

```js
const prepositions = list("prep", "for", "to",  "in", "by", "with");
```

and define a prepositional phrase (e.g., <quote>for the cat</quote>) to be a preposition followed by a noun phrase:

```js
function parse_prepositional_phrase() {      
    return list("prep-phrase",
                parse_word(prepositions),
                parse_noun_phrase());
}
```

现在我们可以将句子定义为名词短语，后跟动词短语，其中动词短语可以是动词，也可以是介词短语扩展的动词短语： [[5]](87#footnote-5)

```js
function parse_sentence() {
    return list("sentence",

}

function parse_verb_phrase() {
    function maybe_extend(verb_phrase) {
        return amb(verb_phrase,
                   maybe_extend(list("verb-phrase",
                                     verb_phrase,
                                     parse_prepositional_phrase())));
    }		   
    return maybe_extend(parse_word(verbs));
}
```

在讨论的同时，我们还可以详细说明名词短语的定义，以允许诸如&lt;quote&gt;之类的东西出现在课堂上。&lt;/quote&gt; 我们过去所说的名词短语，现在称为简单名词短语，名词短语现在将是简单名词短语或由介词短语扩展的名词短语：

```js
function parse_simple_noun_phrase() {      
    return list("simple-noun-phrase",
                parse_word(articles),
                parse_word(nouns));
}

function parse_noun_phrase() {
    function maybe_extend(noun_phrase) {
        return amb(noun_phrase,
                   maybe_extend(list("noun-phrase",
                                     noun_phrase,
                                     parse_prepositional_phrase())));
    }
    return maybe_extend(parse_simple_noun_phrase());
}
```

我们的新语法使我们可以解析更复杂的句子。 例如

```js
parse_input(list("the", "student", "with", "the", "cat", "sleeps", "in", "the", "class"));
```

produces

```js
["sentence",
 [["noun-phrase",
  [["simple-noun-phrase", 
    [["article", ["the", null]],
     [["noun", ["student", null]],
      null],
     ["prep-phrase", 
      [["prep", ["with", null]],
       [["simple-noun-phrase",
        [["article", ["the", null]],
         [["noun", ["cat", null]]],
          null]]]]]],
     [["verb-phrase",
      [["verb", ["sleeps", null]],
       [["prep-phrase",
        [["prep", ["in", null]],
         [["simple-noun-phrase", 
          [["article", ["the", null]],
           ["noun", ["class", null]]]]]]]]]]
```

观察到一个给定的输入可能具有多个合法的解析。 在&lt;quote&gt;句中，教授与猫一起向学生讲课，&lt;/quote&gt;可能是教授与猫一起讲课，或者学生有猫。 我们的不确定性程序会发现两种可能性：

```js
parse_input(list("the", "professor", "lectures",
                 "to", "the", "student", "with", "the", "cat"));
```

produces

```js
["sentence",
 [["simple-noun-phrase", 
   [["article", ["the", null]],
    [["noun", ["professor", null]],
     null]],
    null]],
   [["verb-phrase",
    [["verb-phrase",
     [["verb", ["lectures", null]],
      [["prep-phrase",
        [["prep", ["to", null]],
         ["simple-noun-phrase", 
          [["article", ["the", null]],
           [["noun", ["student", null]],
       [["prep-phrase",
         [["prep", ["with", null]],
          ["simple-noun-phrase", 
           [["article", ["the", null]],
            [["noun", ["cat", null]]]]]]]]]]]
```

要求评估者重试一次

```js
["sentence",
 [["simple-noun-phrase", 
   [["article", ["the", null]],
    [["noun", ["professor", null]],
     null]],
    null]],
   [["verb-phrase",
    [["verb-phrase",
     [["verb", ["lectures", null]],
      [["prep-phrase",
        [["prep", ["to", null]],
         [["noun-phrase", 
          [["simple-noun-phrase", 
          [["article", ["the", null]],
           [["noun", ["student", null]],
          [["prep-phrase",
         [["prep", ["with", null]],
          ["simple-noun-phrase", 
           [["article", ["the", null]],
            [["noun", ["cat", null]]]]]]]]]]]
```

<exercise>With the grammar given above, the following sentence can be parsed in five different ways: <quote>The professor lectures to the student in the class with the cat.</quote> Give the five parses and explain the differences in shades of meaning among them.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>The evaluators in sections <ref name="sec:mc-eval">[4.1](73)</ref> and <ref name="sec:lazy-evaluation">[4.2](81)</ref> do not determine what order operands are evaluated in. We will see that the `amb` evaluator evaluates them from left to right. Explain why our parsing program wouldn't work if the operands were evaluated in some other order.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Louis Reasoner suggests that, since a verb phrase is either a verb or a verb phrase followed by a prepositional phrase, it would be much more straightforward to define the function `parse-verb-phrase` as follows (and similarly for noun phrases):

```js
function parse_verb_phrase() {
    return amb(parse_word(verbs),      
               list("verb-phrase",
                    parse_verb_phrase(),
                    parse_prepositional_phrase()));
}
```

Does this work? Does the program's behavior change if we interchange the order of expressions in the `amb`?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Extend the grammar given above to handle more complex sentences. For example, you could extend noun phrases and verb phrases to include adjectives and adverbs, or you could handle compound sentences.[[6]](87#footnote-6)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Alyssa P. Hacker is more interested in generating interesting sentences than in parsing them. She reasons that by simply changing the function `parse_word` so that it ignores the <quote>input sentence</quote> and instead always succeeds and generates an appropriate word, we can use the programs we had built for parsing to do generation instead. Implement Alyssa's idea, and show the first half-dozen or so sentences generated.[[7]](87#footnote-7)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_87_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

* * *

[[1]](87#footnote-link-1) Our program uses the following function to determine if the elements of a list are distinct:

```js
function distinct(items) {	
    return is_null items 
        ? true
        : is_null(tail(items))
          ? true
          : is_null(member(head(items), tail(items)))
            ? distinct(tail(items))
            : false;
}
```

The function `member_equal` is like `member` except that it uses `equal` instead of `===` to test for equality.

[[2]](87#footnote-link-2) This is taken from a booklet called <quote>Problematical Recreations,</quote> published in the 1960s by Litton Industries, where it is attributed to the _Kansas State Engineer_.

[[3]](87#footnote-link-3) Here we use the convention that the first element of each list designates the part of speech for the rest of the words in the list.

[[4]](87#footnote-link-4) Notice that `parse_word`, uses assignment to modify the unparsed input list. For this to work, our `amb` evaluator must undo the effects of assignment operations when it backtracks.

[[5]](87#footnote-link-5) Observe that this definition is recursive—a verb may be followed by any number of prepositional phrases.

[[6]](87#footnote-link-6) This kind of grammar can become arbitrarily complex, but it is only a toy as far as real language understanding is concerned. Real natural-language understanding by computer requires an elaborate mixture of syntactic analysis and interpretation of meaning. On the other hand, even toy parsers can be useful in supporting flexible command languages for programs such as information-retrieval systems. <citation>Winston 1992</citation> discusses computational approaches to real language understanding and also the applications of simple grammars to command languages.

[[7]](87#footnote-link-7) Although Alyssa's idea works just fine (and is surprisingly simple), the sentences that it generates are a bit boring—they don't sample the possible sentences of this language in a very interesting way. In fact, the grammar is highly recursive in many places, and Alyssa's technique <quote>falls into</quote> one of these recursions and gets stuck. See exercise <ref name="ex:ramb">[4.41](88#ex_4.41)</ref> for a way to deal with this.

