# 2.2.3序列作为常规接口

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

在处理复合数据时，我们强调了数据抽象如何允许我们设计程序而不会陷入数据表示的细节中，以及抽象如何为我们保留尝试替代表示的灵活性。 在本节中，我们介绍了用于数据结构的另一种强大的设计原理-使用_常规接口_。

在 [1.3](18)[2.2.2](31) 节的`count_leaves`函数，该函数将一棵树作为参数并计算叶子平方的和 那很奇怪：

```js
function sum_odd_squares(tree) {
    return is_null(tree)
           ? 0
           : ! is_pair(tree)
             ? (is_odd(tree) ? square(tree) : 0)
             : sum_odd_squares(head(tree))
               +
               sum_odd_squares(tail(tree));
}
```

从表面上看，此函数与以下函数非常不同，后者构造了所有偶数斐波纳契数![%7B%5Ctextrm%7BFib%7D%7D%28k%29](img/db51a3495f80339c2dfc129ec4ce925f.jpg)的列表，其中![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg)小于或等于给定的整数![n](img/493731e423d5db62086d0b8705dda0c8.jpg)：

```js
function even_fibs(n) {
    function next(k) {
        if (k > n) {
            return null;
        } else {
            const f = fib(k);
            return is_even(f)
                   ? pair(f, next(k + 1))
                   : next(k + 1);
        }
    }
    return next(0);
}
```

尽管这两个函数在结构上有很大不同，但对这两个计算的更抽象描述却显示出很多相似之处。 第一个程序

*   枚举一棵树的叶子；
*   过滤它们，选择奇数；
*   平方每个选定的； 和
*   使用`+`累积结果，从0开始。

第二程序

*   枚举从0到![n](img/493731e423d5db62086d0b8705dda0c8.jpg)的整数；
*   计算每个整数的斐波那契数；
*   过滤它们，选择偶数； 和
*   从空列表开始，使用`pair`累积结果。

信号处理工程师会很自然地根据流经级联的信号来概念化这些过程，每个级都实现了程序计划的一部分，如图 [2.7](32#fig_2.7)信号&lt;/quote&gt;。 该信号通过_滤波器_传递，该滤波器消除了所有奇数元素。 结果信号依次通过_映射_，它是将`square`功能应用于每个元素的&lt;quote&gt;换能器&lt;/quote&gt;。 然后，将映射的输出馈送到_累加器_，该累加器使用`+`从初始0开始组合元素。`even_fibs`的计划与此类似。 &lt;split&gt;![](img/8dcde1a91aaacf9c68e78b7ad0fd8a13.jpg)&lt;/split&gt;

不幸的是，以上两个功能定义未能显示出这种信号流结构。 例如，如果我们检查`sum_odd_squares`函数，我们发现枚举部分地由`is_null`和`is_pair`测试实现，而部分地由该函数的树递归结构实现。 同样，在测试中发现累积，部分在递归中使用累加。 通常，每个功能都没有与信号流描述中的元素相对应的不同部分。 我们的两个函数以不同的方式分解计算，将枚举分布在程序上，并将其与映射，过滤器和累加混合在一起。 如果我们可以组织程序使信号流结构体现在我们编写的函数中，那么这将提高所得代码的概念清晰度。

## 序列运算

组织程序以更清楚地反映信号流结构的关键是集中于从过程中的一个阶段流向下一阶段的&lt;quote&gt;信号&lt;/quote&gt;。 如果我们将这些信号表示为列表，则可以在每个阶段使用列表操作来实现处理。 例如，我们可以使用 [2.2.1](30) 部分中的`map`函数来实现信号流程图的映射阶段：

```js
map(square, list(1, 2, 3, 4, 5));
```

过滤序列以仅选择满足给定谓词的那些元素可通过以下方法完成

```js
function filter(predicate, sequence) {
    return is_null(sequence)
           ? null
           : predicate(head(sequence))
             ? pair(head(sequence), 
                    filter(predicate, tail(sequence)))
             : filter(predicate, tail(sequence));
}
```

例如，

```js
filter(is_odd, list(1, 2, 3, 4, 5));
```

Accumulations can be implemented by

```js
function accumulate(op, initial, sequence) {
    return is_null(sequence)
           ? initial
           : op(head(sequence), 
                accumulate(op, initial, tail(sequence)));
}
```

```js
accumulate(plus, 0, list(1, 2, 3, 4, 5));
```

```js
accumulate(times, 1, list(1, 2, 3, 4, 5));
```

```js
accumulate(pair, null, list(1, 2, 3, 4, 5));
```

实现信号流程图所需要做的就是枚举要处理的元素的顺序。 对于`even_fibs`，我们需要生成给定范围内的整数序列，可以执行以下操作：

```js
function enumerate_interval(low, high) {
    return low > high
           ? null
           : pair(low,
                  enumerate_interval(low + 1, high));
}
```

要枚举一棵树的叶子，我们可以使用 [[1]](32#footnote-1)

```js
function enumerate_tree(tree) {
    return is_null(tree)
           ? null
           : ! is_pair(tree)
             ? list(tree)
             : append(enumerate_tree(head(tree)),
                      enumerate_tree(tail(tree)));
}
```

现在，我们可以像信号流图中一样重新构建`sum_odd_squares`和`even_fibs`。 对于`sum_odd_squares`，我们枚举了树的叶子序列，对其进行过滤以仅保留序列中的奇数，对每个元素求平方，并对结果求和：

```js
function sum_odd_squares(tree) {
    return accumulate(plus, 
                      0, 
                      map(square, 
                          filter(is_odd, 
                                 enumerate_tree(tree))));
}
```

对于`even_fibs`，我们枚举从0到![n](img/493731e423d5db62086d0b8705dda0c8.jpg)的整数，为这些整数中的每一个生成斐波那契数，过滤结果序列以仅保留偶数元素，并将结果累加到列表中：

```js
function even_fibs(n) {
    return accumulate(pair, 
                      null, 
                      filter(is_even, 
                             map(fib, 
                                 enumerate_interval(0, n))));
}
```

将程序表示为序列操作的价值在于，这有助于我们进行模块化的程序设计，即通过组合相对独立的片段而构建的设计。 我们可以通过提供标准组件库以及用于灵活连接组件的常规接口来鼓励模块化设计。

模块化构造是控制工程设计复杂性的强大策略。 例如，在实际的信号处理应用中，设计人员通过级联从标准化滤波器和换能器系列中选择的元素来定期构建系统。 类似地，序列操作提供了我们可以混合和匹配的标准程序元素库。 例如，我们可以在构造第一个![n%2B1](img/003fdff6c68a3686101a32ddf3a4ad8b.jpg)斐波纳契数的平方的列表的程序中重用`sum_odd_squares`和`even-fibs`函数的片段：

```js
function list_fib_squares(n) {
    return accumulate(pair, 
                      null, 
                      map(square, 
                          map(fib, 
                              enumerate_interval(0, n))));
}
```

我们可以重新排列片段，并将它们用于计算序列中奇数整数的乘积：

```js
function product_of_squares_of_odd_elements(sequence) {
    return accumulate(times, 
                      1, 
                      map(square, 
                          filter(is_odd, sequence)));
}
```

我们还可以根据序列运算来制定常规的数据处理应用程序。 假设我们有一系列人事记录，并且想要找到薪水最高的程序员的薪水。 假设我们有一个选择器`salary`返回记录的薪水，还有一个谓词`is_programmer`来测试记录是否适合程序员。 然后我们可以写

```js
function salary_of_highest_paid_programmer(records) {
    return accumulate(math_max, 
                      0, 
                      map(salary, 
                          filter(is_programmer, records)));
}
```

这些示例仅暗示可以表示为序列操作的大量操作。 [[2]](32#footnote-2)

在这里以列表形式实现的序列用作常规接口，允许我们组合处理模块。 此外，当我们将结构统一表示为序列时，我们已经将程序中的数据结构相关性本地化为少量的序列操作。 通过更改这些内容，我们可以尝试使用序列的其他表示形式，同时保持程序的整体设计不变。 当我们概括序列处理范例以接纳无限序列时，我们将在 [3.5](66) 部分中利用此功能。

<exercise>Fill in the missing expressions to complete the following definitions of some basic list-manipulation operations as accumulations:

```js
function map(f, sequence) {
    return accumulate((x, y) => ?? , 
                      null, sequence);
}

function append(seq1, seq2) {
    return accumulate(pair, ??, ??);
}

function length(sequence) {
    return accumulate(??, 0, sequence);
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_1_div">Solution</button> <solution>```js
function map(f, sequence) {
    return accumulate((x, y) => pair(p(x), y),
                      null, 
                      sequence);
}

function append(seq1, seq2) {
    return accumulate(pair, seq2, seq1);
}

function length(sequence) {
    return accumulate((x, y) => y + 1,
                      0, 
                      sequence);
}
```</solution></exercise> <exercise>Evaluating a polynomial in ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg) at a given value of ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg) can be formulated as an accumulation. We evaluate the polynomial ![%20a_%7Bn%7D%20x%5En%20%2Ba_%7Bn-1%7Dx%5E%7Bn-1%7D%2B%5Ccdots%20%2B%20a_%7B1%7D%20x%2Ba_%7B0%7D%20](img/bec96049ddbf585c2f76862f81126f21.jpg) using a well-known algorithm called _Horner's rule_, which structures the computation as ![%20%5Cleft%28%5Ccdots%20%28a_%7Bn%7D%20%20x%2Ba_%7Bn-1%7D%29x%2B%5Ccdots%20%2Ba_%7B1%7D%5Cright%29%20x%2Ba_%7B0%7D%20](img/382467d8df262ffd5f3dbcbce0180179.jpg) In other words, we start with ![a_%7Bn%7D](img/ee4908539eced31a42ee24bd21ab1703.jpg), multiply by ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg), add ![a_%7Bn-1%7D](img/a1bb9b3f15897a0ff1c6b11a664cf657.jpg), multiply by ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg), and so on, until we reach ![a_%7B0%7D](img/cc93c014ed48cb15fa104d99620959d6.jpg).[[3]](32#footnote-3) Fill in the following template to produce a function that evaluates a polynomial using Horner's rule. Assume that the coefficients of the polynomial are arranged in a sequence, from ![a_%7B0%7D](img/cc93c014ed48cb15fa104d99620959d6.jpg) through ![a_%7Bn%7D](img/ee4908539eced31a42ee24bd21ab1703.jpg).

```js
function horner_eval(x, coefficient_sequence) {
    return accumulate((this_coeff, higher_terms) => ??,
                      0, 
                      coefficient_sequence);
}
```

For example, to compute ![1%2B3x%2B5x%5E3%2Bx%5E5](img/c69a9c9bb6ac368aaa10649d5d8dece4.jpg) at ![x%3D2](img/61de87e5dfd77bdcf1e5e9d0904d481e.jpg) you would evaluate

```js
horner_eval(2, list(1, 3, 0, 5, 0, 1));
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_2_div">Solution</button> <solution>```js
function horner_eval(x, coefficient_sequence) {
    return accumulate((this_coeff, higher_terms) => 
                      x * higher_terms + this_coeff,
                      0, 
                      coefficient_sequence);
}
```</solution></exercise> <exercise>Redefine `count_leaves` from section <ref name="sec:trees">[2.2.2](31)</ref> as an accumulation:

```js
function count_leaves(t) {
    return accumulate(??, ??, map(??, ??));
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_3_div">Solution</button> <solution>```js
function count_leaves(t) {
    return accumulate((leaves, total) => leaves + total,
                      0, 
		      map(sub_tree => is_pair(sub_tree)
		             ? count_leaves(sub_tree)
                             : 1,
			  t));
}
```</solution></exercise> <exercise>The function `accumulate_n` is similar to `accumulate` except that it takes as its third argument a sequence of sequences, which are all assumed to have the same number of elements. It applies the designated accumulation function to combine all the first elements of the sequences, all the second elements of the sequences, and so on, and returns a sequence of the results. For instance, if `s` is a sequence containing four sequences

```js
list(list( 1,  2,  3),
     list( 4,  5,  6),
     list( 7,  8,  9),
     list(10, 11, 12))
```

then the value of `accumulate_n(plus, 0, s)` should be the sequence `[22, [26, [30, null]]]`. Fill in the missing expressions in the following definition of `accumulate_n`:

```js
function accumulate_n(op, init, seqs) {
    return is_null(head(seqs))
           ? null
           : pair(accumulate(op, init, ??), 
                  accumulate_n(op, init, ??));
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_4_div">Solution</button> <solution>```js
function accumulate_n(op, init, seqs) {
    return is_null(head(seqs))
           ? null
           : pair(accumulate(op, init, map(x => head(x), seqs)),
                  accumulate_n(op, init, map(x => tail(x), seqs)));
}
```</solution></exercise> <exercise>Suppose we represent vectors ![v%3D%28v_%7Bi%7D%29](img/e9aa8d65c9970a934e0e6940a156a1a0.jpg) as sequences of numbers, and matrices ![m%3D%28m_%7Bij%7D%29](img/3d4b49834d6c11a21c47fb351861f104.jpg) as sequences of vectors (the rows of the matrix). For example, the matrix ![%20%5Cleft%5B%20%0A%20%20%20%20%20%20%20%20%20%20%5Cbegin%7Barray%7D%7Bllll%7D%0A%20%20%20%20%20%20%20%20%20%201%20%26%202%20%26%203%20%26%204%5C%5C%0A%20%20%20%20%20%20%20%20%20%204%20%26%205%20%26%206%20%26%206%5C%5C%0A%20%20%20%20%20%20%20%20%20%206%20%26%207%20%26%208%20%26%209%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%5Cend%7Barray%7D%0A%20%20%20%20%20%20%20%20%20%20%5Cright%5D%20](img/afdaa11f3feaf8fe6a16d1d99a9cdccd.jpg) <split>is represented as the following sequence:

```js
[[1, [2, [3, [4, null]]]], 
 [[4, [5, [6, [6, null]]]], 
  [[6, [7, [8, [9, null]]]], null]]]
```</split> With this representation, we can use sequence operations to concisely express the basic matrix and vector operations. These operations (which are described in any book on matrix algebra) are the following:

*   `dot_product(` ![v](img/a7b0439b75ff3fa542a86700c6afc69a.jpg) `, ` ![w](img/f16e78fda8eb72a97d51d051c1695a2b.jpg) `)`返回总和![%5Csum_%7Bi%7Dv_%7Bi%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20w_%7Bi%7D](img/b40b0ef51ce60da81f58c131ddfd24ba.jpg)。
*   `matrix_times_vector(` ![m](img/20ddd8181c2e0d0fb893637e8572d475.jpg) `, ` ![v](img/a7b0439b75ff3fa542a86700c6afc69a.jpg) `)`返回向量![t](img/654b00d1036ba7f7d93e02f57fc00a75.jpg)，其中![t_%7Bi%7D%20%3D%5Csum_%7Bj%7Dm_%7Bij%7Dv_%7Bj%7D](img/3a66f3e68c7fd68380eb841cbad8a8e7.jpg)。
*   `matrix_times_matrix(` ![m](img/20ddd8181c2e0d0fb893637e8572d475.jpg) `, ` ![n](img/493731e423d5db62086d0b8705dda0c8.jpg) `)`返回矩阵![p](img/648811253cdbfe19389964c25be56518.jpg)，其中![p_%7Bij%7D%3D%5Csum_%7Bk%7D%20m_%7Bik%7Dn_%7Bkj%7D](img/b4702fcc3d9cb48d6535d74b37bef4e8.jpg)。
*   `transpose(` ![m](img/20ddd8181c2e0d0fb893637e8572d475.jpg) `)`返回矩阵![n](img/493731e423d5db62086d0b8705dda0c8.jpg)，其中![n_%7Bij%7D%3Dm_%7Bji%7D](img/928f6b4c07d3d40ecfcaf4adb4f637b8.jpg)。

We can define the dot product as[[4]](32#footnote-4)

```js
function dot_product(v, w) {
    return accumulate(plus, 0, 
                      accumulate_n(times, 1, list(v, w)));
}
```

Fill in the missing expressions in the following functions for computing the other matrix operations. (The function `accumulate_n` is defined in exercise <ref name="ex:accumulate-n">[2.36](32#ex_2.36)</ref>.)

```js
function matrix_times_vector(m, v) {
    return map(??, m);
}

function transpose(mat) {
    return accumulate_n(??, ??, mat);
}

function matrix_times_matrix(n, m) {
    const cols = transpose(n);
    return map(??, m);
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_5_div">Solution</button> <solution>```js
function matrix_times_vector(m, v) {
    return map(row => dot_product(row, v), m);
}

function transpose(mat) {
    return accumulate_n(pair, null, mat);
}

function matrix_times_matrix(n, m) {
    const cols = transpose(m);
    return map(x => map(y => dot_product(x, y), cols), n);
}
```</solution></exercise> <exercise>The `accumulate` function is also known as `fold_right`, because it combines the first element of the sequence with the result of combining all the elements to the right. There is also a `fold_left`, which is similar to `fold_right`, except that it combines elements working in the opposite direction:

```js
function fold_left(op, initial, sequence) {
    function iter(result, rest) {
        return is_null(rest)
               ? result
               : iter(op(result, head(rest)), 
                      tail(rest));
    }
    return iter(initial, sequence);
}
```

What are the values of

```js
fold_right(divide, 1, list(1, 2, 3));
```

```js
fold_left(divide, 1, list(1, 2, 3));
```

```js
fold_right(list, null, list(1, 2, 3));
```

```js
fold_left(list, null, list(1, 2, 3));
```

Give a property that `op` should satisfy to guarantee that `fold_right` and `fold_left` will produce the same values for any sequence.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_6_div">Solution</button><solution>We can guarantee that `fold_right` and `fold_left` produce the same values for any sequence, if we require that `op` is commutative and associative.

```js
fold_right(plus, 0, list(1, 2, 3));
```

```js
fold_left(plus, 0, list(1, 2, 3));
```</solution></exercise> <exercise>Complete the following definitions of `reverse` (exercise <ref name="ex:reverse">[2.18](30#ex_2.18)</ref>) in terms of `fold_right` and `fold_left` from exercise <ref name="ex:fold-right-left">[2.38](32#ex_2.38)</ref>:

```js
function reverse(sequence) {
    return fold_right((x, y) => ??, null, sequence);
}
```

```js
function reverse(sequence) {
    return fold_left((x, y) => ??, null, sequence);
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_7_div">Solution</button> <solution>```js
function reverse(sequence) {
    return fold_right((x, y) => append(y, list(x)), 
                      null, sequence);
}
```

```js
function reverse(sequence) {
    return fold_left((x, y) => pair(y, x), null, sequence);
}
```</solution></exercise> 

## 嵌套映射

我们可以扩展序列范式以包括许多通常使用嵌套循环表示的计算。 [[5]](32#footnote-5)

考虑以下问题：给定正整数![n](img/493731e423d5db62086d0b8705dda0c8.jpg)，找到所有不同的正整数![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg)和![j](img/d8fdd0e28cfb03738fc5227885ee035a.jpg)的所有有序对，其中![1%5Cleq%20j%3C%20i%5Cleq%20n](img/70d82aaccbf460bbcfe7726305fac957.jpg)，使得![i%20%2Bj](img/e6c93c38c47e25239b12e441a4cbe6d4.jpg)是质数。 例如，如果![n](img/493731e423d5db62086d0b8705dda0c8.jpg)为6，则对如下：![%0A%20%20%20%20%20%20%20%20%20%20%5Cbegin%7Barray%7D%7Bc%7Cccccccc%7D%0A%20%20%20%20%20%20%20%20%20%20i%20%20%20%20%20%26%202%20%26%203%20%26%204%20%26%204%20%26%205%20%26%206%20%26%206%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20j%20%20%20%20%20%26%201%20%26%202%20%26%201%20%26%203%20%26%202%20%26%201%20%26%205%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%5Chline%0A%20%20%20%20%20%20%20%20%20%20i%2Bj%20%20%20%26%203%20%26%205%20%26%205%20%26%207%20%26%207%20%26%207%20%26%2011%0A%20%20%20%20%20%20%20%20%20%20%5Cend%7Barray%7D%0A%20%20%20%20%20%20%20%20%20%20](img/2ca803faecd6eb2cfa9d1a40112f16ed.jpg)

进行此计算的一种自然方法是生成所有小于或等于![n](img/493731e423d5db62086d0b8705dda0c8.jpg)的正整数的所有有序对的序列，过滤以选择总和为素数的对，然后对通过的每个对![%28i%2C%20j%29](img/b072c9308ce53186326cd47399f7abfc.jpg) 过滤器，生成三元组![%28i%2C%20j%2C%20i%2Bj%29](img/f74515465be66269179661eeb0e42b2b.jpg)。

这是一种生成对序列的方法：对于每个整数![i%5Cleq%20n](img/1205575ab5bf528ec49c0d3435a79c43.jpg)，枚举整数![j%3Ci](img/b36d6a0659274d8ba392294c146b42d5.jpg)，对于每个这样的![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg)和![j](img/d8fdd0e28cfb03738fc5227885ee035a.jpg)生成对![%28i%2C%20j%29](img/b072c9308ce53186326cd47399f7abfc.jpg)。 在序列操作方面，我们沿着序列`enumerate_interval(1, n)`进行映射。 对于此序列中的每个![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg)，我们都沿序列`enumerate_interval(1, i-1)`进行映射。 对于后一个序列中的每个![j](img/d8fdd0e28cfb03738fc5227885ee035a.jpg)，我们生成对`list(i, j)`。 这给我们每个![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg)的成对序列。 组合所有![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg)的所有序列（通过与`append`累加）产生所需的对序列： [[6]](32#footnote-6)

```js
accumulate(append, 
           null, 
           map(i => map(j => list(i, j),
                        enumerate_interval(1, i-1)),
               enumerate_interval(1, n)));
```

在此类程序中，使用`append`进行映射和累加的组合非常普遍，我们将其隔离为单独的函数：

```js
function flatmap(f, seq) {
    return accumulate(append, null, map(f, seq));
}
```

现在，过滤此对对序列以找到总和为素数的对。 对于该序列的每个元素都调用filter谓词。 它的参数是一对，并且必须从该对中提取整数。 因此，适用于序列中每个元素的谓词为

```js
function is_prime_sum(pair) {
    return is_prime(head(pair) + head(tail(pair)));
}
```

最后，使用以下函数通过映射过滤后的对来生成结果序列，该函数构造一个由该对的两个元素及其和组成的三元组：

```js
function make_pair_sum(pair) {
    return list(head(pair), head(tail(pair)), 
                head(pair) + head(tail(pair)));
}
```

结合所有这些步骤，即可产生完整的功能：

```js
function prime_sum_pairs(n) {
    return map(make_pair_sum, 
             filter(is_prime_sum, 
               flatmap(i => map(j => list(i, j), 
                                enumerate_interval(1, i - 1)),
                       enumerate_interval(1, n))));
}
```

嵌套映射对于除枚举间隔的序列以外的其他序列也很有用。 假设我们希望生成集合![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg)的所有排列； 也就是说，订购集合中项目的所有方式。 例如，![%5C%7B1%2C%202%2C%203%5C%7D](img/4bff663a12e4890d3cc11cab2465c5da.jpg)的排列是![%5C%7B1%2C%202%2C%203%5C%7D](img/4bff663a12e4890d3cc11cab2465c5da.jpg)，![%5C%7B%201%2C%203%2C%202%5C%7D](img/f6af1ab7686785793b7c1a754b88ab10.jpg)，![%5C%7B2%2C%201%2C%203%5C%7D](img/90b2605511dfd46034f71fb0766344b2.jpg)，![%5C%7B%202%2C%203%2C%201%5C%7D](img/c58c283be80a974508d277a96c2331eb.jpg)，![%5C%7B%203%2C%201%2C%202%5C%7D](img/3a14fe19187b7d1c2dd0b85e2c658e6d.jpg)和![%5C%7B%203%2C%202%2C%201%5C%7D](img/1cbcf55bbf7d2ae87261984723bc188a.jpg)。 这是生成![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg)排列的计划：对于![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg)中的每个项目![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)，递归生成![S-x](img/1d70721f7bafb8456f33e53404465232.jpg)， [[7]](32#footnote-7) 的排列序列，并与![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)邻接 每个人的前面。 对于![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg)中的每个![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)，这都会产生以![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)开头的![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg)排列序列。 将所有![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)的序列组合在一起，得出![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg)的所有排列： [[8]](32#footnote-8)

```js
function permutations(s) {
    return is_null(s)
           ? list(null)
           : flatmap(x => map(p => pair(x, p),
                              permutations(remove(x, s))),
                     s);
}
```

注意，该策略如何将生成![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg)的排列问题减少到生成元素集少于![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg)的集合的排列问题。 在终端的情况下，我们一直到空列表，该空列表表示没有元素的集合。 为此，我们生成`list(null)`，它是一个包含一项的序列，即没有元素的集合。 `permutations`中使用的`remove`函数以给定序列返回除给定项目外的所有项目。 这可以表示为一个简单的过滤器：

```js
function remove(item, sequence) {
    return filter(x => !(x === item),
                  sequence);
}
```

<exercise>Define a function `unique_pairs` that, given an integer ![n](img/493731e423d5db62086d0b8705dda0c8.jpg), generates the sequence of pairs ![%28i%2C%20j%29](img/b072c9308ce53186326cd47399f7abfc.jpg) with ![1%5Cleq%20j%3C%20i%5Cleq%20n](img/70d82aaccbf460bbcfe7726305fac957.jpg). Use `unique_pairs` to simplify the definition of `prime_sum_pairs` given above.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_8_div">Solution</button> <solution>```js
function unique_pairs(n) {
    return flatmap(i => map(j => list(i, j), 
                            enumerate_interval(1, i-1)), 
                   enumerate_interval(1, n));
}
function prime_sum_pairs(n) {
    return map(make_pair_sum, 
               filter(is_prime_sum, 
                      unique_pairs(n)));
}
```</solution></exercise> <exercise>Write a function to find all ordered triples of distinct positive integers ![i](img/31df9c730e19ca29b59dce64b99d98c1.jpg), ![j](img/d8fdd0e28cfb03738fc5227885ee035a.jpg), and ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg) less than or equal to a given integer ![n](img/493731e423d5db62086d0b8705dda0c8.jpg) that sum to a given integer ![s](img/0492c0bfd615cb5e61c847ece512ff51.jpg).<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_9_div">Solution</button> <solution>```js
function unique_triples(n) {
    return flatmap(i => flatmap(j => map(k => list(i, j, k),
                                         enumerate_interval(1, j-1)),
                                enumerate_interval(1, i-1)),
                   enumerate_interval(1, n));
}
function plus(x, y) {
    return x + y;
}
function triples_that_sum_to(s, n) {
    return filter(items => accumulate(plus, 0, items) === s,
                  unique_triples(n));
}
```</solution></exercise> <exercise>![](img/dd62cbfc5e52bb683f029027b31f0ea4.jpg) The <quote>eight-queens puzzle</quote> asks how to place eight queens on a chessboard so that no queen is in check from any other (i.e., no two queens are in the same row, column, or diagonal). One possible solution is shown in Figure <ref name="fig:8queens">[2.8](32#fig_2.8)</ref>. One way to solve the puzzle is to work across the board, placing a queen in each column. Once we have placed ![k-1](img/849caa86b524da41e0176331698ace44.jpg) queens, we must place the ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg)th queen in a position where it does not check any of the queens already on the board. We can formulate this approach recursively: Assume that we have already generated the sequence of all possible ways to place ![k-1](img/849caa86b524da41e0176331698ace44.jpg) queens in the first ![k-1](img/849caa86b524da41e0176331698ace44.jpg) columns of the board. For each of these ways, generate an extended set of positions by placing a queen in each row of the ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg)th column. Now filter these, keeping only the positions for which the queen in the ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg)th column is safe with respect to the other queens. This produces the sequence of all ways to place ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg) queens in the first ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg) columns. By continuing this process, we will produce not only one solution, but all solutions to the puzzle. We implement this solution as a function `queens`, which returns a sequence of all solutions to the problem of placing ![n](img/493731e423d5db62086d0b8705dda0c8.jpg) queens on an ![n%5Ctimes%20n](img/ef18fbe2d9aabe8898f46bd8d9dd3804.jpg) chessboard. The function `queens` has an internal function `queens_cols` that returns the sequence of all ways to place queens in the first ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg) columns of the board.

```js
function queens(board_size) {
    function queen_cols(k) {
        return k === 0
               ? list(empty_board)
               : filter(
                     positions => is_safe(k, positions), 
                     flatmap(rest_of_queens => 
			     map(new_row => adjoin_position(
                                             new_row, k,
                                             rest_of_queens), 
                                 enumerate_interval(1, 
                                     board_size)),
			     queen_cols(k - 1)));
   }
   return queen_cols(board_size);
}
```

In this function `rest_of_queens` is a way to place ![k-1](img/849caa86b524da41e0176331698ace44.jpg) queens in the first ![k-1](img/849caa86b524da41e0176331698ace44.jpg) columns, and `new_row` is a proposed row in which to place the queen for the ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg)th column. Complete the program by implementing the representation for sets of board positions, including the function `adjoin_position`, which adjoins a new row-column position to a set of positions, and `empty_board`, which represents an empty set of positions. You must also write the function `is_safe`, which determines for a set of positions, whether the queen in the ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg)th column is safe with respect to the others. (Note that we need only check whether the new queen is safe—the other queens are already guaranteed safe with respect to each other.)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_10_div">Solution</button> <solution>```js
function adjoin_position(row, col, rest) {
    return pair(pair(row, col), rest);
}
```

```js
const empty_board = null;
```

```js
function is_safe(k, positions) {
    const first_row = head(head(positions));
    const first_col = tail(head(positions));
    return accumulate((pos, so_far) => {
                         const row = head(pos);
                         const col = tail(pos);
                         return so_far &&
                                first_row - first_col !==
                                row - col &&
                                first_row + first_col !==
                                row + col &&
                                first_row !== row;
                      },
                      true, 
                      tail(positions));
}
```

Putting it all together:

```js
// click here to see the solution
```</solution></exercise> <exercise>Louis Reasoner is having a terrible time doing exercise <ref name="ex:8queens">[2.42](32#ex_2.42)</ref>. His `queens` function seems to work, but it runs extremely slowly. (Louis never does manage to wait long enough for it to solve even the ![6%5Ctimes%206](img/2c1b10c1af0f581e6fb44696708d6654.jpg) case.) When Louis asks Eva Lu Ator for help, she points out that he has interchanged the order of the nested mappings in the `flatmap`, writing it as

```js
flatmap(new_row =>
        map(rest_of_queens => adjoin_position(
                               new_row, k, 
                               rest_of_queens), 
	    queen_cols(k - 1)), 
        enumerate_interval(1, board_size));
```

Explain why this interchange makes the program run slowly. Estimate how long it will take Louis's program to solve the eight-queens puzzle, assuming that the program in exercise <ref name="ex:8queens">[2.42](32#ex_2.42)</ref> solves the puzzle in time ![T](img/5a047a5ca04e45726dba21b8302977da.jpg).<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_32_11_div">Solution</button><solution>Louis's program re-evaluates the application `queen_cols(k - 1))` in each iteration of `flatmap`, which happens ![n](img/493731e423d5db62086d0b8705dda0c8.jpg) times for each ![k](img/a1c2f8d5b1226e67bdb44b12a6ddf18b.jpg). That means overall Louis's program will solve the puzzle in a time of about ![n%5En%20T](img/fe82c08c4ef4fe2af72f2d608434905a.jpg) if the program in exercise <ref name="ex:8queens">[2.42](32#ex_2.42)</ref> solves the puzzle in time ![T](img/5a047a5ca04e45726dba21b8302977da.jpg).</solution></exercise>

* * *

[[1]](32#footnote-link-1) This is, in fact, precisely the `fringe` function from exercise <ref name="ex:fringe">[2.28](31#ex_2.28)</ref>. Here we've renamed it to emphasize that it is part of a family of general sequence-manipulation functions.

[[2]](32#footnote-link-2) Richard Waters (1979) developed a program that automatically analyzes traditional Fortran programs, viewing them in terms of maps, filters, and accumulations. He found that fully 90 percent of the code in the Fortran Scientific Subroutine Package fits neatly into this paradigm. One of the reasons for the success of Lisp as a programming language is that lists provide a standard medium for expressing ordered collections so that they can be manipulated using higher-order operations. The programming language APL owes much of its power and appeal to a similar choice. In APL all data are represented as arrays, and there is a universal and convenient set of generic operators for all sorts of array operations.

[[3]](32#footnote-link-3) According to Knuth (1981), this rule was formulated by W. G. Horner early in the nineteenth century, but the method was actually used by Newton over a hundred years earlier. Horner's rule evaluates the polynomial using fewer additions and multiplications than does the straightforward method of first computing ![a_%7Bn%7D%20x%5En](img/cd2b9f26f4688be4524e6b8116524fa7.jpg), then adding ![a_%7Bn-1%7Dx%5E%7Bn-1%7D](img/b3a4fedb5afb73200fd19146f29fc1ad.jpg), and so on. In fact, it is possible to prove that any algorithm for evaluating arbitrary polynomials must use at least as many additions and multiplications as does Horner's rule, and thus Horner's rule is an optimal algorithm for polynomial evaluation. This was proved (for the number of additions) by A. M. Ostrowski in a 1954 paper that essentially founded the modern study of optimal algorithms. The analogous statement for multiplications was proved by V. Y. Pan in 1966\. The book by <citation>鲍罗丁和芒罗（1975）

Borodin and Munro 1975</citation> provides an overview of these and other results about optimal algorithms.

[[4]](32#footnote-link-4) This definition uses the function `accumulate_n` from exercise <ref name="ex:accumulate-n">[2.36](32#ex_2.36)</ref>.

[[5]](32#footnote-link-5) This approach to nested mappings was shown to us by David Turner, whose languages KRC and Miranda provide elegant formalisms for dealing with these constructs. The examples in this section (see also exercise <ref name="ex:8queens">[2.42](32#ex_2.42)</ref>) are adapted from Turner 1981\. In section <ref name="sec:exploiting-streams">[3.5.3](69)</ref>, we'll see how this approach generalizes to infinite sequences.

[[6]](32#footnote-link-6) We're representing a pair here as a list of two elements rather than as an ordinary pair. Thus, the <quote>pair</quote> ![%28i%2C%20j%29](img/b072c9308ce53186326cd47399f7abfc.jpg) is represented as `list(i, j)`, not `pair(i, j)`.

[[7]](32#footnote-link-7) The set ![S-x](img/1d70721f7bafb8456f33e53404465232.jpg) is the set of all elements of ![S](img/988eb79721742b0fc7fee7147ab57d0c.jpg), excluding ![x](img/40779fc60a53ff2b70f832ec10cade09.jpg).

[[8]](32#footnote-link-8) The character sequence `//` in JavaScript code is used to introduce _comments_. Everything from `//` to the end of the line is ignored by the interpreter. In this book we don't use many comments; we try to make our programs self-documenting by using descriptive names.

