# 2.2.2层次结构

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

用列表表示序列的方法自然可以概括为表示其元素本身可以是序列的序列。 例如，我们可以考虑由以下对象构造的对象`[[1, [2, null]], [3, [4, null]]]`

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

as a list of three items, the first of which is itself a list, `[1, [2, null]]`. Figure <ref name="fig:cons-of-2-lists">[2.5](31#fig_2.5)</ref> shows the representation of this structure in terms of pairs. <split>![](img/dddd2ee021a60cdecedcdbea2217edb5.jpg)</split> 

认为元素是序列的序列的另一种方法是_树_。 序列的元素是树的分支，本身是序列的元素是子树。 图 [2.6](31#fig_2.6)[2.5](31#fig_2.5)![](img/74c9a05eeb5dfe9baf4fa70e350f7b6c.jpg)&lt;/split&gt;

递归是处理树结构的自然工具，因为我们经常可以将对树的操作减少为对其分支的操作​​，这依次减少为对分支的分支的操作​​，依此类推，直到到达树的叶子为止 。 例如，将 [2.2.1](30) 部分的`length`函数与`count_leaves`函数进行比较，该函数返回树的叶子总数：

```js
const x = pair(pair(1, pair(2,null)), pair(3, pair(4,null)));
```

```js
length(x);
// 3
```

```js
count_leaves(x);
// 4
```

```js
list(x, x);
// [[[[1, [2, null]], [3, [4, null]]],
//  [[[1, [2, null]], [3, [4, null]]], null]]
```

```js
length(list(x, x));
// 2
```

```js
count_leaves(list(x, x));
// 8
```

要实现`count_leaves`，请调用计算`length`的递归计划：

*   列表`x`的`length`是1加上`x`的`tail`的`length`。
*   空列表的`length`为0。

The function `count_leaves` is similar. The value for the empty list is the same:

*   空列表的`count_leaves`为0。

But in the reduction step, where we strip off the `head` of the list, we must take into account that the `head` may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is

*   树`x`的`count_leaves`是`x`的`head`的`count_leaves`加上`x`的`tail`的`count_leaves`。

Finally, by taking `head`s we reach actual leaves, so we need another base case:

*   叶子的`count_leaves`为1。

To aid in writing recursive functions on trees, our JavaScript environment provides the primitive predicate `is_pair`, which tests whether its argument is a pair. Here is the complete function:

```js
function count_leaves(x) {
    return is_null(x)
           ? 0
           : ! is_pair(x)
             ? 1
             : count_leaves(head(x)) +
               count_leaves(tail(x));
}
```

<exercise>Suppose we evaluate the expression `list(1, list(2, list(3, 4)))`. Give the result printed by the interpreter, the corresponding box-and-pointer structure, and the interpretation of this as a tree (as in Figure <ref name="fig:list-as-tree-javascript">[2.6](31#fig_2.6)</ref>).<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_1_div">Solution</button> <solution>1.  &lt;snippet eval="no"&gt;&lt;name&gt;count_leaves_example_result&lt;/name&gt; [1，[[2，[[3，[4，null]]，null]]，null]]&lt;/snippet&gt;
2.  ![](img/4a6f351ec24d2726985eceaf92b119b5.jpg)
3.  ![](img/5e5322fabe4a1d418b1c68ffb8f862e9.jpg)</solution></exercise> <exercise>Give combinations of `head`s and `tail`s that will pick 7 from each of the following lists<split>, given as printed by our interpreter:</split>

```js
[1, [3, [[5, [7, null]], [9, null]]]]

[[7, null], null]

[1,
 [
  [2,
   [
    [3, 
     [
      [4, 
       [
        [5, 
         [
          [6, 
           [7, 
            null
           ]
          ], 
          null
         ]
        ], 
        null
       ]
      ], 
      null
     ]
    ], 
    null
   ]
  ], 
  null
 ]
]
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_2_div">Solution</button> <solution>*   ```js
    head(tail(head(tail(tail(the_first_list)))));
    ```

*   ```js
    head(head(the_second_list));
    ```

*   ```js
    head(tail(head(tail(head(tail(head(tail(head(
        tail(head(tail(the_third_list))))))))))));
    ```</solution></exercise> <exercise>Suppose we define `x` and `y` to be two lists:

```js
const x = list(1, 2, 3);

const y = list(4, 5, 6);
```

What result is printed by the interpreter in response to evaluating each of the following expressions:

```js
append(x, y);
```

```js
pair(x, y);
```

```js
list(x, y);
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_3_div">Solution</button> <solution>1.  ```js
    [1, [2, [3, [4, [5, [6, null]]]]]]
    ```

2.  ```js
    [[1, [2, [3, null]]], [4, [5, [6, null]]]]
    ```

3.  ```js
    [[1, [2, [3, null]]], [[4, [5, [6, null]]], null]]
    ```</solution></exercise> <exercise>Modify your `reverse` function of exercise <ref name="ex:reverse">[2.18](30#ex_2.18)</ref> to produce a `deep_reverse` function that takes a list as argument and returns as its value the list with its elements reversed and with all sublists deep-reversed as well. For example,

```js
const x = list(list(1, 2), list(3, 4));
```

```js
x;
// [[1, [2, null]], [[3, [4, null]], null]]
```

```js
reverse(x);
// [[3, [4, null]], [[1, [2, null]], null]]
```

```js
deep_reverse(x);
// [[4, [3, null]], [[2, [1, null]], null]]
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_4_div">Solution</button> <solution>```js
function deep_reverse(items){
    return is_null(items)
           ? null
           : is_pair(items)
             ? append(deep_reverse(tail(items)), 
                      pair(deep_reverse(head(items)), 
                           null))
	     : items;
}
```</solution></exercise> <exercise>Write a function `fringe` that takes as argument a tree (represented as a list) and returns a list whose elements are all the leaves of the tree arranged in left-to-right order. For example,

```js
const x = list(list(1, 2), list(3, 4));
```

```js
fringe(x);
// [1, [2, [3, [4, null]]]]
```

```js
fringe(list(x, x));
// [1, [2, [3, [4, [1, [2, [3, [4, null]]]]]]]]
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_5_div">Solution</button> <solution>```js
function fringe(x) {
    return is_null(x)
           ? null
           : is_pair(x)
             ? append(fringe(head(x)), fringe(tail(x)))
             : list(x);
}
```</solution></exercise> <exercise>A binary mobile consists of two branches, a left branch and a right branch. Each branch is a rod of a certain length, from which hangs either a weight or another binary mobile. We can represent a binary mobile using compound data by constructing it from two branches (for example, using `list`):

```js
function make_mobile(left, right) {
   return list(left, right);
}
```

A branch is constructed from a `length` (which must be a number) together with a `structure`, which may be either a number (representing a simple weight) or another mobile:

```js
function make_branch(length, structure) {
   return list(length, structure);
}
```

1.  写下相应的选择器`left_branch`和`right_branch`（它们返回移动设备的分支），以及`branch_length`和`branch_structure`（它们返回分支的组件）。
2.  使用选择器定义一个函数`total_weight`，该函数返回手机的总重量。
3.  如果移动设备的左上分支施加的扭矩等于其右上分支施加的扭矩（即，如果左杆的长度乘以重量悬挂，则称其为_平衡_） 杆上的距离等于右侧的相应乘积），并且悬挂在其分支上的每个子车是否平衡。 设计一个谓词，以测试二进制移动设备是否平衡。
4.  假设我们更改移动设备的表示形式，以便构造函数为

    ```js
    function make_mobile(left, right) {
       return pair(left, right);
    }
    function make_branch(length, structure) {
       return pair(length, structure);
    }
    ```

    ，那么您需要多少更改程序才能转换为新的表示形式？

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_6_div">Solution</button> <solution>1.  ```js
    function left_branch(m) {
        return head(m);
    }
    function right_branch(m) {
        return head(tail(m));
    }
    function branch_length(b) {
        return head(b);
    }
    function branch_structure(b) {
        return head(tail(b));
    }
    ```

2.  ```js
    function is_weight(x){
        return is_number(x);
    }
    function total_weight(x) {
        return is_weight(x)
            ? x
            : total_weight(branch_structure(
                             left_branch(x))) +
              total_weight(branch_structure(
                             right_branch(x)));
    }
    ```

3.  ```js
    function is_balanced(x) {
        return is_weight(x) ||
            ( is_balanced(branch_structure(
                                      left_branch(x))) &&
              is_balanced(branch_structure(
                                      right_branch(x))) &&
              total_weight(branch_structure(
                                      left_branch(x))) 
              * branch_length(left_branch(x))
              ===
              total_weight(branch_structure(
                                      right_branch(x))) 
              * branch_length(right_branch(x))
            );
    }
    ```

4.  使用此替代表示，移动和分支的访问器功能需要进行如下更改：

    ```js
    function left_branch(m) {
      return head(m);
    }
    function right_branch(m) {
      return tail(m);
    }
    function branch_length(b) {
      return head(b);
    }
    function branch_structure(b) {
      return tail(b);
    }
    ```</solution></exercise> 

## 映射树

就像`map`是处理序列的强大抽象一样，`map`与递归一起也是处理树的强大抽象。 例如，类似于 [2.2.1](30) 部分的`scale_list`的`scale_tree`函数，将数字因子和叶子为数字的树作为参数。 它返回形状相同的树，其中每个数字都乘以系数。 `scale_tree`的递归计划类似于`count_leaves`的递归计划：

```js
function scale_tree(tree, factor) {
    return is_null(tree)
           ? null
           : ! is_pair(tree)
             ? tree * factor
             : pair(scale_tree(head(tree), factor), 
                    scale_tree(tail(tree), factor));
}
```

实现`scale_tree`的另一种方法是将树视为子树序列，并使用`map`。 我们在序列上进行映射，依次缩放每个子树，然后返回结果列表。 在基本情况下，如果树是叶子，我们只需乘以以下因子：

```js
function scale_tree(tree, factor) {
    return map(sub_tree => is_pair(sub_tree)
                           ? scale_tree(sub_tree, factor)
                           : sub_tree * factor, 
               tree);
}
```

许多树操作可以通过序列操作和递归的类似组合来实现。

<exercise>Define a function `square_tree` analogous to the `square_list` function of exercise <ref name="ex:square-list">[2.21](30#ex_2.21)</ref>. That is, `square_tree` should behave as follows:

```js
square_tree(list(1,
                 list(2, list(3, 4), 5), 
                 list(6, 7)));
// result: [1, [[4, [[9, [16, null]], 
//                   [25, null]]], 
//              [[36, [49, null]], null]]]
```

Define `square_tree` both directly (i.e., without using any higher-order functions) and also by using `map` and recursion.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_7_div">Solution</button><solution>Directly:

```js
function square_tree(tree) {
    return is_null(tree)
           ? null
           : ! is_pair(tree)
             ? square(tree)
             : pair(square_tree(head(tree)),
	            square_tree(tail(tree)));
}
```

The version using `map`:

```js
function square_tree(tree) {
    return map(sub_tree => ! is_pair(sub_tree)
                           ? square(sub_tree)
                           : square_tree(sub_tree),
               tree);
}
```</solution></exercise> <exercise>Abstract your answer to exercise <ref name="ex:square-tree">[2.30](31#ex_2.30)</ref> to produce a function `tree_map` with the property that `square_tree` could be defined as

```js
function square_tree(tree) {
   return tree_map(square, tree);
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_8_div">Solution</button> <solution>```js
function tree_map(f, tree) {
    return map(sub_tree => is_null(sub_tree)
                           ? null
                           : is_pair(sub_tree)
                             ? tree_map(f, sub_tree)
                             : f(sub_tree),
               tree);
}
```</solution></exercise> <exercise>We can represent a set as a list of distinct elements, and we can represent the set of all subsets of the set as a list of lists. For example, if the set is `[1, [2, [3, null]]]`, then the set of all subsets looks as follows:

```js
[null, [[3, null], [[2, null], [[2, [3, null]], 
    [[1, null], [[2, [3, null]], [[1, [2, null]], 
        [[1, [2, [3, null]]], null]]]]]]]]
```

Complete the following definition of a function that generates the set of subsets of a set and give a clear explanation of why it works:

```js
function subsets(s) {
    if (is_null(s)) {
        return list(null);
    } else {
        const rest = subsets(tail(s));
        return append(rest, map(??, rest));
    }
}
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_31_9_div">Solution</button> <solution>```js
function subsets(s) {
    if (is_null(s)) {
        return list(null);
    } else {
        const rest = subsets(tail(s));
        return append(rest, map(x => pair(head(s), x), rest));
    }
}
```

The argument starts in a similar way as the argument for the function `cc` in section <ref name="sec:tree-recursion">[1.2.2](13)</ref>: A subset either contains the first element ![e](img/be8982d125e27260b5c793cf0d39d70a.jpg) of the given set, or it doesn't. If it doesn't, the problem becomes strictly smaller: Compute all subsets of the tail of the list that represents the given set. If it does, it must result from adding ![e](img/be8982d125e27260b5c793cf0d39d70a.jpg) to a subset that doesn't contain ![e](img/be8982d125e27260b5c793cf0d39d70a.jpg). In the end, we need to append both lists of subsets to obtain the list of all subsets.</solution></exercise>