作业5: 面向对象编程, 链表, 树
Homework 5: Object-Oriented Programming, Linked Lists, Trees

Due by 11:59pm on Monday, October 26

查看英文原文

说明

下载 hw05.zip。在压缩包中,你会找到一个名为 hw05.py 的文件,以及一份 ok 自动评分器。

提交:完成后使用python3 ok --submit来提交代码。你可以在截止日期前多次提交;只有最后一次提交会被评分。请检查你是否成功提交了代码到okpy.org。更多提交作业的说明请参见Lab0

使用Ok:如果你对使用Ok有任何疑问,请参考本指南

阅读材料:以下阅读材料可能对你有帮助

评分:作业评分基于正确性。每个错误的问题将使总分减少一分。课程大纲中有作业恢复政策。 本次作业满分为2分。

必答题

面向对象编程 (OOP)

Q1: 自动售货机

创建一个名为 VendingMachine 的类,它表示一个用于售卖某些商品的自动售货机。 VendingMachine 对象应返回描述其交互过程的字符串。

请补全 VendingMachine 类,添加适当的属性和方法,使其行为符合以下的 doctests:

class VendingMachine:
    """A vending machine that vends some product for some price.

    >>> v = VendingMachine('candy', 10)
    >>> v.vend()
    'Inventory empty. Restocking required.'
    >>> v.add_funds(15)
    'Inventory empty. Restocking required. Here is your $15.'
    >>> v.restock(2)
    'Current candy stock: 2'
    >>> v.vend()
    'You must add $10 more funds.'
    >>> v.add_funds(7)
    'Current balance: $7'
    >>> v.vend()
    'You must add $3 more funds.'
    >>> v.add_funds(5)
    'Current balance: $12'
    >>> v.vend()
    'Here is your candy and $2 change.'
    >>> v.add_funds(10)
    'Current balance: $10'
    >>> v.vend()
    'Here is your candy.'
    >>> v.add_funds(15)
    'Inventory empty. Restocking required. Here is your $15.'

    >>> w = VendingMachine('soda', 2)
    >>> w.restock(3)
    'Current soda stock: 3'
    >>> w.restock(3)
    'Current soda stock: 6'
    >>> w.add_funds(2)
    'Current balance: $2'
    >>> w.vend()
    'Here is your soda.'
    """
    "*** YOUR CODE HERE ***"

Python 字符串格式化语法 可能对你有用。 下面是一个示例:

>>> ten, twenty, thirty = 10, 'twenty', [30]
>>> '{0} plus {1} is {2}'.format(ten, twenty, thirty)
'10 plus twenty is [30]'

使用 Ok 来测试你的代码:

python3 ok -q VendingMachine --local

Q2: 铸币

完成 MintCoin 类,使铸币厂创建的硬币具有正确的年份和价值。

  • 每个 Mint 实例都有一个 year 标记。update 方法将 year 标记设置为 Mint 类中的 current_year 类属性的值。
  • create 方法接受 Coin 的子类,并返回该类的一个实例,该实例的年份标记为当前铸币厂 mint 的年份 (如果 mint 未调用 update 方法,其年份可能不同于 Mint.current_year)。
  • Coin 类的 worth 方法返回硬币的面值 cents,并且对于超过 50 年的硬币, 每多一年增加 1 cent。硬币的年龄可以通过用 Mint 类的 current_year 类属性减去硬币的年份来计算。
class Mint:
    """A mint creates coins by stamping on years.

    The update method sets the mint's stamp to Mint.current_year.

    >>> mint = Mint()
    >>> mint.year
    2020
    >>> dime = mint.create(Dime)
    >>> dime.year
    2020
    >>> Mint.current_year = 2100  # Time passes
    >>> nickel = mint.create(Nickel)
    >>> nickel.year     # The mint has not updated its stamp yet
    2020
    >>> nickel.worth()  # 5 cents + (80 - 50 years)
    35
    >>> mint.update()   # The mint's year is updated to 2100
    >>> Mint.current_year = 2175     # More time passes
    >>> mint.create(Dime).worth()    # 10 cents + (75 - 50 years)
    35
    >>> Mint().create(Dime).worth()  # A new mint has the current year
    10
    >>> dime.worth()     # 10 cents + (155 - 50 years)
    115
    >>> Dime.cents = 20  # Upgrade all dimes!
    >>> dime.worth()     # 20 cents + (155 - 50 years)
    125
    """
    current_year = 2020

    def __init__(self):
        self.update()

    def create(self, kind):
        "*** YOUR CODE HERE ***"

    def update(self):
        "*** YOUR CODE HERE ***"

class Coin:
    def __init__(self, year):
        self.year = year

    def worth(self):
        "*** YOUR CODE HERE ***"

class Nickel(Coin):
    cents = 5

class Dime(Coin):
    cents = 10

使用 Ok 来测试你的代码:

python3 ok -q Mint --local

链表

Q3: 存储数位

编写一个函数 store_digits,它接收一个整数 n,并返回一个链表, 该链表的每个元素都是 n 的一个数位。

注意:不要使用任何字符串操作函数,如 strreversed

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
    """
    "*** YOUR CODE HERE ***"

使用 Ok 来测试你的代码:

python3 ok -q store_digits --local

附加题

Q4: 判断二叉搜索树

编写一个函数 is_bst,它接收一棵树 t,并在且仅在 t 是一棵有效的二叉搜索树时返回 True。二叉搜索树需满足以下条件:

  • 每个节点最多有两个子节点(叶子节点自动视为有效的二叉搜索树)。
  • 所有子树本身也是有效的二叉搜索树。
  • 对于每个节点,该节点左子树中的所有值小于等于该节点的标签。
  • 对于每个节点,该节点右子树中的所有值大于该节点的标签。

以下是一个二叉搜索树的示例:

bst

请注意,如果一个节点只有一个子节点,该子节点可以被视为左子节点或右子节点。你需要将这一点考虑在内。

提示: 编写辅助函数 bst_minbst_max 可能会有所帮助,它们分别返回一棵树的最小值和最大值(前提是该树是有效的二叉搜索树)。

def is_bst(t):
    """Returns True if the Tree t has the structure of a valid BST.

    >>> t1 = Tree(6, [Tree(2, [Tree(1), Tree(4)]), Tree(7, [Tree(7), Tree(8)])])
    >>> is_bst(t1)
    True
    >>> t2 = Tree(8, [Tree(2, [Tree(9), Tree(1)]), Tree(3, [Tree(6)]), Tree(5)])
    >>> is_bst(t2)
    False
    >>> t3 = Tree(6, [Tree(2, [Tree(4), Tree(1)]), Tree(7, [Tree(7), Tree(8)])])
    >>> is_bst(t3)
    False
    >>> t4 = Tree(1, [Tree(2, [Tree(3, [Tree(4)])])])
    >>> is_bst(t4)
    True
    >>> t5 = Tree(1, [Tree(0, [Tree(-1, [Tree(-2)])])])
    >>> is_bst(t5)
    True
    >>> t6 = Tree(1, [Tree(4, [Tree(2, [Tree(3)])])])
    >>> is_bst(t6)
    True
    >>> t7 = Tree(2, [Tree(1, [Tree(5)]), Tree(4)])
    >>> is_bst(t7)
    False
    """
    "*** YOUR CODE HERE ***"

使用 Ok 来测试你的代码:

python3 ok -q is_bst --local

Q5: 前序遍历

定义函数 preorder,该函数接受一棵树作为参数,并返回一个列表,包含树中所有的元素,顺序与 print_tree 打印它们的顺序相同。

下图展示了节点的打印顺序,箭头表示函数调用的顺序。

前序遍历

注意:这种节点的遍历顺序称为前序遍历(preorder traversal)。

def preorder(t):
    """Return a list of the entries in this tree in the order that they
    would be visited by a preorder traversal (see problem description).

    >>> numbers = Tree(1, [Tree(2), Tree(3, [Tree(4), Tree(5)]), Tree(6, [Tree(7)])])
    >>> preorder(numbers)
    [1, 2, 3, 4, 5, 6, 7]
    >>> preorder(Tree(2, [Tree(4, [Tree(6)])]))
    [2, 4, 6]
    """
    "*** YOUR CODE HERE ***"

使用 Ok 来测试你的代码:

python3 ok -q preorder --local

生成器/树

Q6: 生成路径

定义一个生成器函数 path_yielder,它接受一棵树 t 和一个值 value,返回一个生成器对象,该生成器会生成从 t 的根节点到标签为 value 的节点的所有路径。

t 是使用类实现的,而不是基于函数的ADT(抽象数据类型)。

每条路径应该以列表的形式表示,其中包含树中沿路径的所有标签。路径的生成顺序可以是任意的。

我们已经为你提供了一个(部分)代码框架。你不一定要使用这个框架,但如果你的实现与其有较大不同,你可能需要思考如何调整你的代码以适配该框架。

def path_yielder(t, value):
    """Yields all possible paths from the root of t to a node with the label value
    as a list.

    >>> t1 = Tree(1, [Tree(2, [Tree(3), Tree(4, [Tree(6)]), Tree(5)]), Tree(5)])
    >>> print(t1)
    1
      2
        3
        4
          6
        5
      5
    >>> next(path_yielder(t1, 6))
    [1, 2, 4, 6]
    >>> path_to_5 = path_yielder(t1, 5)
    >>> sorted(list(path_to_5))
    [[1, 2, 5], [1, 5]]

    >>> t2 = Tree(0, [Tree(2, [t1])])
    >>> print(t2)
    0
      2
        1
          2
            3
            4
              6
            5
          5
    >>> path_to_2 = path_yielder(t2, 2)
    >>> sorted(list(path_to_2))
    [[0, 2], [0, 2, 1, 2]]
    """

    "*** YOUR CODE HERE ***"

    for _______________ in _________________:
        for _______________ in _________________:

            "*** YOUR CODE HERE ***"

提示:如果你不知道如何开始,可以思考如果这不是一个生成器函数,你会如何解决这个问题?你的递归调用会是什么样的?在一个生成器函数中,如果你在函数体内进行“递归调用”,会发生什么?

使用 Ok 来测试你的代码:

python3 ok -q path_yielder --local