# 4.3.1搜寻和搜寻

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

为了扩展JavaScript以支持不确定性，我们引入了一种称为`amb`的新型表达式。 [[1]](86#footnote-1)

表达式`amb(` ![e_1%2C%5C%20e_2%2C%5Cldots%2C%20e_n](img/c4149a20e61205617e21735653a24844.jpg) `)`模糊地返回![n](img/493731e423d5db62086d0b8705dda0c8.jpg)表达式![e_i](img/184816479e41ce4520100444e70686c8.jpg) &lt;quote&gt;之一的值。&lt;/quote&gt; 例如，

```js
list(amb(1, 2, 3), amb("a", "b"))
```

can have six possible values:

```js
list(1, "a") list(1, "b") list(2, "a") list(2, "b") list(3, "a") list(3, "b")
```

单选`amb`的应用程序会产生一个普通（单个）值。

没有选择的`amb`应用程序-表达式`amb()`-是没有可接受值的表达式。 在操作上，我们可以将`amb()`视为一个表达式，当对其求值时，该计算将导致&lt;quote&gt;失败&lt;/quote&gt;：计算中止并且不产生任何值。 使用这个想法，我们可以表达一个特定谓词表达式`p`必须为真的要求，如下所示：

```js
function require(p) {
    return ! p ? amb() : "some ordinary value";      
}
```

使用`amb`和`require`，我们可以实现上面使用的`an_element_of`函数：

```js
function an_element_of(items) {
    require(! is_null(items));
    return amb(head(items), an_element_of(tail(items));
}
```

如果列表为空，则`an_element_of`的应用失败。 否则，它将模棱两可地返回列表的第一个元素或从列表的其余部分中选择的元素。

我们还可以表达无限的选择范围。 以下函数可能返回任何大于或等于给定![n](img/493731e423d5db62086d0b8705dda0c8.jpg)的整数：

```js
function an_integer_starting_from(n) {
    return amb(n, an_integer_starting_from(n + 1));
}
```

类似于 [3.5.2](68) 部分中介绍的流函数`integers_starting_from`，但有一个重要区别：流函数返回一个对象，该对象表示以[开头的所有整数的序列 HTG6]，而`amb`函数返回一个整数。 [[2]](86#footnote-2)

抽象地讲，我们可以想象对`amb`表达式求值会导致时间分成多个分支，在该分支上，使用该表达式的可能值之一继续在每个分支上进行计算。 我们说`amb`代表_不确定选择点_。 如果我们有一台可以动态分配足够数量处理器的机器，那么我们可以以一种简单的方式实现搜索。 执行将像在顺序机器中一样进行，直到遇到`amb`表达式为止。 此时，将分配并初始化更多处理器，以继续该选择所隐含的所有并行执行。 每个处理器都将按照顺序进行处理，就像它是唯一的选择一样，直到遇到故障而终止，或者进一步细分，或者完成为止。 [[3]](86#footnote-3)

另一方面，如果我们有一台只能执行一个进程（或几个并发进程）的机器，则必须按顺序考虑其他方案。 可以想象修改一个评估程序以在遇到选择点时随机选择要遵循的分支。 但是，随机选择很容易导致失败的值。 我们可能尝试一遍又一遍地运行评估程序，做出随机选择并希望找到一个不失败的值，但是最好_系统搜索_所有可能的执行路径。 我们将在本节中开发和使用的`amb`评估程序将按如下方式进行系统搜索：当评估程序遇到`amb`的应用程序时，它将首先选择第一个替代方案。 这种选择本身可能会导致进一步的选择。 评估人员将始终在每个选择点最初选择第一个替代方案。 如果选择导致失败，则评估程序会自动 [[4]](86#footnote-4) _回溯_到最近的选择点，并尝试下一个选择。 如果在任何选择点都用尽替代品，则评估人员将备份到先前的选择点，然后从那里继续。 该过程导致了一种搜索策略，称为_深度优先搜索_或_按时间顺序回溯_。 [[5]](86#footnote-5)

## 驱动循环

`amb`评估程序的驱动程序循环具有一些异常的属性。 如上面显示的 &lt;spltinline&gt;`prime_sum_pair`&lt;/spltinline&gt; 示例一样，它读取表达式并打印第一个非失败执行的值。 如果我们想查看下一次成功执行的值，可以要求解释器回溯并尝试生成第二个非失败执行。 通过键入`try-again`发出信号。 如果给出除`try-again`以外的任何其他输入，则解释器将开始一个新问题，并丢弃先前问题中未探索的替代方案。 这是一个示例交互：

```js
// Amb-Eval input:
prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));
// Starting a new problem
// Amb-Eval value:
[3, [20, null]]

// Amb-Eval input:
try-again
// Amb-Eval value:
[3, [110, null]]

// Amb-Eval input:
try-again
// Amb-Eval value:
[8, [35, null]]

// Amb-Eval input:
try-again
// There are no more values of
prime_sum_pair([1, [3, [5, [8, null]]]], [20, [35, [110, null]]])

// Amb-Eval input:
prime_sum_pair(list(19, 27, 30), list(11, 36, 58));
// Starting a new problem
// Amb-Eval value:
[30, [11, null]]
```

<exercise>Write a function `an_integer_between` that returns an integer between two given bounds. This can be used to implement a function that finds Pythagorean triples, i.e., triples of integers ![%28i%2Cj%2Ck%29](img/cccce8ae59c67b10c828612e22f98387.jpg) between the given bounds such that ![i%20%5Cleq%20j](img/13fb427315c5e76e9e9043f458927aa0.jpg) and ![i%5E2%20%2B%20j%5E2%20%3Dk%5E2](img/eb310183f83b18a7d1a30c42df37aa31.jpg), as follows:

```js
function a_pythogorean_triple_between(low, high) {      
    const i = an_integer_between(low, high);
    const j = an_integer_between(i, high);
    const k = an_integer_between(j, high);
    require(i * i + j * j === k * k);
    return list(i, j, k);
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_86_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:stream-pythagorean-triples">[3.69](69#ex_3.69)</ref> discussed how to generate the stream of _all_ Pythagorean triples, with no upper bound on the size of the integers to be searched. Explain why simply replacing `an_integer_between` by `an_integer_starting_from` in the function in exercise <ref name="ex:amb-pythag-triples">[4.26](86#ex_4.26)</ref> is not an adequate way to generate arbitrary Pythagorean triples. Write a function that actually will accomplish this. (That is, write a function for which repeatedly typing would in principle eventually generate all Pythagorean triples.)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_86_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>Ben Bitdiddle claims that the following method for generating Pythagorean triples is more efficient than the one in exercise <ref name="ex:amb-pythag-triples">[4.26](86#ex_4.26)</ref>. Is he correct? (Hint: Consider the number of possibilities that must be explored.)

```js
function a_pythagorean_triple_between(low, high) {
    const i = an_integer_between(low, high);
    const hsq = high * high;
    const j = an_integer_between(i, high);
    const ksq = i * i + j * j;
    require(hsq >= ksq);
    const k = sqrt(ksq);
    require(is_integer(k));
    list(i, j, k);
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_86_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]](86#footnote-link-1) The idea of `amb` for nondeterministic programming was first described in 1961 by John McCarthy (see <citation>McCarthy 1967</citation>). For convenience, we make these expressions look like function applications. This is analogous to the lazy boolean operators, which look like binary operators, but which are treated differently by the evaluator. Applications of `amb` will be treated differently from applications of ordinary primitive functions.

[[2]](86#footnote-link-2) In actuality, the distinction between nondeterministically returning a single choice and returning all choices depends somewhat on our point of view. From the perspective of the code that uses the value, the nondeterministic choice returns a single value. From the perspective of the programmer designing the code, the nondeterministic choice potentially returns all possible values, and the computation branches so that each value is investigated separately.

[[3]](86#footnote-link-3) One might object that this is a hopelessly inefficient mechanism. It might require millions of processors to solve some easily stated problem this way, and most of the time most of those processors would be idle. This objection should be taken in the context of history. Memory used to be considered just such an expensive commodity. In 1964 a megabyte of RAM cost about <dollar></dollar>400,000. Now every personal computer has many megabytes of RAM, and most of the time most of that RAM is unused. It is hard to underestimate the cost of mass-produced electronics.

[[4]](86#footnote-link-4) Automagically: <quote>Automatically, but in a way which, for some reason (typically because it is too complicated, or too ugly, or perhaps even too trivial), the speaker doesn't feel like explaining.</quote> (<citation>Steele 1983</citation>, <citation>Raymond 1993</citation>)

[[5]](86#footnote-link-5) The integration of automatic search strategies into programming languages has had a long and checkered history. The first suggestions that nondeterministic algorithms might be elegantly encoded in a programming language with search and automatic backtracking came from Robert Floyd (1967). Carl Hewitt (1969) invented a programming language called Planner that explicitly supported automatic chronological backtracking, providing for a built-in depth-first search strategy. Sussman, Winograd, and Charniak (1971) implemented a subset of this language, called MicroPlanner, which was used to support work in problem solving and robot planning. Similar ideas, arising from logic and theorem proving, led to the genesis in Edinburgh and Marseille of the elegant language Prolog (which we will discuss in section <ref name="sec:logic-programming">[4.4](89)</ref>). After sufficient frustration with automatic search, McDermott and Sussman (1972) developed a language called Conniver, which included mechanisms for placing the search strategy under programmer control. This proved unwieldy, however, and Sussman and Stallman (1975) found a more tractable approach while investigating methods of symbolic analysis for electrical circuits. They developed a non-chronological backtracking scheme that was based on tracing out the logical dependencies connecting facts, a technique that has come to be known as _dependency-directed backtracking_. Although their method was complex, it produced reasonably efficient programs because it did little redundant search. <citation>Doyle 1979</citation> and McAllester (<citation>McAllester 1978</citation>, <citation>McAllester 1980</citation>) generalized and clarified the methods of Stallman and Sussman, developing a new paradigm for formulating search that is now called _truth maintenance_. Modern problem-solving systems all use some form of truth-maintenance system as a substrate. See <citation>Forbus and deKleer 1993</citation> for a discussion of elegant ways to build truth-maintenance systems and applications using truth maintenance. <citation>Zabih, McAllester, and Chapman 1987</citation> describes a nondeterministic extension to Scheme that is based on `amb`; it is similar to the interpreter described in this section, but more sophisticated, because it uses dependency-directed backtracking rather than chronological backtracking. <citation>Winston 1992</citation> gives an introduction to both kinds of backtracking.

