下载 lab07.zip 。在该压缩包中,你将找到本实验中问题的起始文件,以及 Ok 自动评分器的副本。
如果你需要复习本实验的材料,请参考本节。可以直接跳到 问题 上,如果卡住了,可以回到这里。
我们已经知道, Python 列表是顺序存储值的一种方式。另一种类型的列表是一个链表。一个 Python 列表将其所有的元素存储在一个对象中,每个元素都可以通过使用其索引来访问。另一方面,一个链表是一个递归对象,它只存储两样东西:它的第一个值和对列表其余部分的引用,这是另一个链表。
我们可以实现一个类, Link ,它代表一个链表对象。 Link 的每个实例都有两个实例属性, first 和 rest 。
class Link:
    """A linked list.
    >>> s = Link(1)
    >>> s.first
    1
    >>> s.rest is Link.empty
    True
    >>> s = Link(2, Link(3, Link(4)))
    >>> s.first = 5
    >>> s.rest.first = 6
    >>> s.rest.rest = Link.empty
    >>> s                                    # Displays the contents of repr(s)
    Link(5, Link(6))
    >>> s.rest = Link(7, Link(Link(8, Link(9))))
    >>> s
    Link(5, Link(7, Link(Link(8, Link(9)))))
    >>> print(s)                             # Prints str(s)
    <5 7 <8 9>>
    """
    empty = ()
    def __init__(self, first, rest=empty):
        assert rest is Link.empty or isinstance(rest, Link)
        self.first = first
        self.rest = rest
    def __repr__(self):
        if self.rest is not Link.empty:
            rest_repr = ', ' + repr(self.rest)
        else:
            rest_repr = ''
        return 'Link(' + repr(self.first) + rest_repr + ')'
    def __str__(self):
        string = '<'
        while self.rest is not Link.empty:
            string += str(self.first) + ' '
            self = self.rest
        return string + str(self.first) + '>'
一个有效的链表可以是以下的一种:
Link.empty)Link 对象使得链表具有递归性的是,单个 Link 实例的 rest 属性是另一个链表!从大的方面看,每个 Link 实例都存储了列表的一个值。当多个 Link 通过每个实例的 rest 属性链接在一起时,就形成了一个完整的序列。
注意: 这个定义意味着任何
Link实例的rest属性都必须是Link.empty或另一个Link实例!这在Link.__init__中被强制执行,如果传递进来的rest的值不是这些东西,它会引发一个AssertionError。
要检查一个链表是否为空,可以将其与类属性 Link.empty 进行比较。例如,下面的函数会打印出它所处理的链接是否为空:
def test_empty(link):
    if link is Link.empty:
        print('This linked list is empty!')
    else:
        print('This linked list is not empty!')
在计算机科学中, 树 是一种递归数据结构,被广泛用于各种场合,可以用多种方式实现。下图是一个树的例子。

一般来说,在计算机科学中,你可能会看到像这样“颠倒”地画树。我们说 根 是树在顶部开始分支的节点,而 叶 是树在底部结束的节点。
关于树的一些术语:
1 号节点。4、 5、 6、 2 节点是叶子。3 号节点的深度为 1 , 4 号节点的深度为 2 ,因为根和它本身之间没有边,所以根的深度为 0 。4、 5、 6节点都是最低的叶子,深度为 2 。 因此,整个树的高度为 2 。在计算机科学中,有许多不同类型的树,用于不同的目的。有些在每个节点的分支数量上有所不同;有些则在树的结构上有所不同。
一棵树有一个根值和一个分支列表,其中每个分支本身就是一棵树。
Tree 的构造函数接收一个根的值 label ,以及一个可选的 branches 列表。如果没有给出 branches ,构造函数使用空列表 [] 作为默认值。t 的标签,我们访问实例属性 t.label 。t.branches 将给我们一个 分支的列表 。考虑到这一点,我们可以使用我们的构造函数创建先前的树:
t = Tree(1,
      [Tree(3,
          [Tree(4),
           Tree(5),
           Tree(6)]),
      Tree(2)])
将树作为一个类来实现给了我们另一个好处:我们可以通过实现 __repr__ 和 __str__ 方法来指定我们希望它们如何被解释器输出。
这里是 __repr__ 方法:
def __repr__(self):
    if self.branches:
        branch_str = ', ' + repr(self.branches)
    else:
        branch_str = ''
    return 'Tree({0}{1})'.format(self.label, branch_str)
通过这个 __repr__ 的实现,一个 Tree 实例被显示为创建它的确切构造函数调用:
>>> t = Tree(4, [Tree(3), Tree(5, [Tree(6)]), Tree(7)])
>>> t
Tree(4, [Tree(3), Tree(5, [Tree(6)]), Tree(7)])
>>> t.branches
[Tree(3), Tree(5, [Tree(6)]), Tree(7)]
>>> t.branches[0]
Tree(3)
>>> t.branches[1]
Tree(5, [Tree(6)])
这里是 __str__ 方法。你不需要了解这个函数是如何实现的。
def __str__(self):
    def print_tree(t, indent=0):
        tree_str = '  ' * indent + str(t.label) + "\n"
        for b in t.branches:
            tree_str += print_tree(b, indent + 1)
        return tree_str
    return print_tree(self).rstrip()
通过这个 __str__ 的实现,我们可以漂亮地打印一个 Tree ,以看到它的内容和结构:
>>> t = Tree(4, [Tree(3), Tree(5, [Tree(6)]), Tree(7)])
>>> print(t)
4
  3
  5
    6
  7
>>> print(t.branches[0])
3
>>> print(t.branches[1])
5
  6
仔细阅读 lab07.py 中的 Link 类。确保你理解了。
用 Ok 来测试你对以下“Python会显示什么?”问题的认识:
python3 ok -q link -u如果你认为答案是
<function ...>,则输入Function,如果出错则输入Error,如果没有显示则输入Nothing。如果你被卡住了,可以试着在纸上画出链表的框和指向图,或者用
python3 -i lab07.py将Link类加载到解释器中。
>>> from lab07 import *
>>> link = Link(1000)
>>> link.first
______
>>> link.rest is Link.empty
______
>>> link = Link(1000, 2000)
______
>>> link = Link(1000, Link())
______
>>> from lab07 import *
>>> link = Link(1, Link(2, Link(3)))
>>> link.first
______
>>> link.rest.first
______
>>> link.rest.rest.rest is Link.empty
______
>>> link.first = 9001
>>> link.first
______
>>> link.rest = link.rest.rest
>>> link.rest.first
______
>>> link = Link(1)
>>> link.rest = link
>>> link.rest.rest.rest.rest.first
______
>>> link = Link(2, Link(3, Link(4)))
>>> link2 = Link(1, link)
>>> link2.first
______
>>> link2.rest.first
______
>>> from lab07 import *
>>> link = Link(5, Link(6, Link(7)))
>>> link                  # Look at the __repr__ method of Link
______
>>> print(link)          # Look at the __str__ method of Link
______
要解决这些问题,请打开 Parsons 编辑器:
python3 parsons
编写一个函数,接收一个链表并返回该链表的反转版本(元素的顺序相反)。它不应该改变原始列表。
>>> s = Link(1, Link(2, Link(3, Link.empty)))
>>> reverse_link(s)
Link(3, Link(2, Link(1)))
>>> s
Link(1, Link(2, Link(3)))
>>> k = Link(3, Link(5, Link(7, Link(9))))
>>> reverse_link(k)
Link(9, Link(7, Link(5, Link(3))))
>>> k
Link(3, Link(5, Link(7, Link(9))))
提示: 你应该对链表进行迭代。如果你在开始时遇到困难,可以参考下图。

def reverse_link(lnk):
    """
    Given a linked list lnk, return a new linked list which has all the
    elements of lnk but in reverse order.
    >>> s = Link(1, Link(2, Link(3, Link.empty)))
    >>> reverse_link(s)
    Link(3, Link(2, Link(1)))
    >>> s
    Link(1, Link(2, Link(3)))
    >>> k = Link(3, Link(5, Link(7, Link(9))))
    >>> reverse_link(k)
    Link(9, Link(7, Link(5, Link(3))))
    >>> k
    Link(3, Link(5, Link(7, Link(9))))
    """
    "*** YOUR CODE HERE ***"
编写一个函数 label_multiplier ,它接收一个 Tree 和一个整数 val 。 label_multiplier 应该通过将其原始值乘以 val 来改变树的标签。
>>> t1 = Tree(2, [Tree(4, [Tree(6)]), Tree(8)])
>>> label_multiplier(t1, 10)
>>> t1
Tree(20, [Tree(40, [Tree(60)]), Tree(80)])
>>> t2 = Tree(10, [Tree(9), Tree(8, [Tree(7), Tree(6)]), Tree(5, [Tree(4), Tree(3), Tree(2)])])
>>> label_multiplier(t2, 3)
>>> t2
Tree(30, [Tree(27), Tree(24, [Tree(21), Tree(18)]), Tree(15, [Tree(12), Tree(9), Tree(6)])])
def label_multiplier(t, val):
    """
    Given a tree t, mutate t so that all of the tree's
    labels are multiplied by the argument val.
    >>> t1 = Tree(2, [Tree(4, [Tree(6)]), Tree(8)])
    >>> label_multiplier(t1, 10)
    >>> t1
    Tree(20, [Tree(40, [Tree(60)]), Tree(80)])
    >>> t2 = Tree(10, [Tree(9), Tree(8, [Tree(7), Tree(6)]), Tree(5, [Tree(4), Tree(3), Tree(2)])])
    >>> label_multiplier(t2, 3)
    >>> t2
    Tree(30, [Tree(27), Tree(24, [Tree(21), Tree(18)]), Tree(15, [Tree(12), Tree(9), Tree(6)])])
    """
    "*** YOUR CODE HERE ***"
编写一个函数 store_digits ,接收一个整数 n 并返回一个链表,列表中的每个元素是 n 的每一个数字。
重要提示: 不要使用任何字符串操作函数,如
str和reversed。
def store_digits(n):
    """Stores the digits of a positive number n in a linked list.
    >>> s = store_digits(1)
    >>> s
    Link(1)
    >>> store_digits(2345)
    Link(2, Link(3, Link(4, Link(5))))
    >>> store_digits(876)
    Link(8, Link(7, Link(6)))
    >>> # a check for restricted functions
    >>> import inspect, re
    >>> cleaned = re.sub(r"#.*\\n", '', re.sub(r'"{3}[\s\S]*?"{3}', '', inspect.getsource(store_digits)))
    >>> print("Do not use str or reversed!") if any([r in cleaned for r in ["str", "reversed"]]) else None
    >>> link1 = Link(3, Link(Link(4), Link(5, Link(6))))
    """
    "*** YOUR CODE HERE ***"
用 Ok 来测试你的代码:
python3 ok -q store_digits
编写一个函数 cumulative_mul 来突变树 t ,使每个节点的标签变成为它的标签与其的子树标签的乘积。
提示: 仔细考虑何时对树进行改变,以及改变应该在处理子树之前还是之后发生。
def cumulative_mul(t):
    """Mutates t so that each node's label becomes the product of all labels in
    the corresponding subtree rooted at t.
    >>> t = Tree(1, [Tree(3, [Tree(5)]), Tree(7)])
    >>> cumulative_mul(t)
    >>> t
    Tree(105, [Tree(15, [Tree(5)]), Tree(7)])
    >>> otherTree = Tree(2, [Tree(1, [Tree(3), Tree(4), Tree(5)]), Tree(6, [Tree(7)])])
    >>> cumulative_mul(otherTree)
    >>> otherTree
    Tree(5040, [Tree(60, [Tree(3), Tree(4), Tree(5)]), Tree(42, [Tree(7)])])
    """
    "*** YOUR CODE HERE ***"
用 Ok 来测试你的代码:
python3 ok -q cumulative_mul
请确保提交本实验:
python3 ok --submit
Link 类可以表示带有循环的列表。也就是说,一个列表可以包含自己作为一个子列表。
>>> s = Link(1, Link(2, Link(3)))
>>> s.rest.rest.rest = s
>>> s.rest.rest.rest.rest.rest.first
3
实现 has_cycle ,返回其参数,即 Link 实例,是否包含一个循环。
提示: 遍历链表并尝试跟踪你已经看到的
Link对象。
def has_cycle(link):
    """Return whether link contains a cycle.
    >>> s = Link(1, Link(2, Link(3)))
    >>> s.rest.rest.rest = s
    >>> has_cycle(s)
    True
    >>> t = Link(1, Link(2, Link(3)))
    >>> has_cycle(t)
    False
    >>> u = Link(2, Link(2, Link(2)))
    >>> has_cycle(u)
    False
    """
    "*** YOUR CODE HERE ***"
用 Ok 来测试你的代码:
python3 ok -q has_cycle
额外的挑战(可选): 实现 has_cycle ,而不跟踪你已经看到的所有 Link 对象。这个解决方案很短(不到 20 行代码),但需要一个聪明的想法。在询问之前,请尝试自己发现这个解决方案。
def has_cycle_constant(link):
    """Return whether link contains a cycle.
    >>> s = Link(1, Link(2, Link(3)))
    >>> s.rest.rest.rest = s
    >>> has_cycle_constant(s)
    True
    >>> t = Link(1, Link(2, Link(3)))
    >>> has_cycle_constant(t)
    False
    """
    "*** YOUR CODE HERE ***"
用 Ok 来测试你的代码:
python3 ok -q has_cycle_constant