首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >日拱一卒,伯克利CS61A,有这么扎实的作业,还愁学生实力不强吗?

日拱一卒,伯克利CS61A,有这么扎实的作业,还愁学生实力不强吗?

作者头像
TechFlow-承志
发布2022-09-21 12:44:33
发布2022-09-21 12:44:33
1.1K0
举报
文章被收录于专栏:TechFlowTechFlow

作者 | 梁唐

出品 | 公众号:Coder梁(ID:Coder_LT)

大家好,日拱一卒,我是梁唐。

我们继续来看伯克利公开课CS61A,这一次我们一起来做一下作业5,原链接:https://inst.eecs.berkeley.edu//~cs61a/sp18/hw/hw05/#mobiles

这一次的作业是一个双周作业,因此题量比较大,一共有17题。除了最后一道题之外,难度还可以,非常锻炼人,但比之前的一些难题要稍稍简单一些。所以我决定分成两篇写完,最后一题单独一篇,剩余的16题一篇。

这一次的题目同样关于函数式编程以及递归,加入了一些面向对象设计的部分。

基本上可以这么说,只要认认真真不看答案把这些题做完,基本上对于递归以及Python函数式编程的理解就算是初出茅庐了。

好了,废话不多说,我们直接开始看题吧。

Q1: Replace Leaf

公开课里老师讲解了通过Python实现树结构的方式,如果大家没有看过对应的课程也没有关系,其实只是使用递归对树结构进行定义。它的核心只有几个函数,代码如下:

代码语言:javascript
复制
def tree(label, branches=[]):
    """Construct a tree with the given label value and a list of branches."""
    for branch in branches:
        assert is_tree(branch), 'branches must be trees'
    return [label] + list(branches)

def label(tree):
    """Return the label value of a tree."""
    return tree[0]

def branches(tree):
    """Return the list of branches of the given tree."""
    return tree[1:]

def is_tree(tree):
    """Returns True if the given tree is a tree, and False otherwise."""
    if type(tree) != list or len(tree) < 1:
        return False
    for branch in branches(tree):
        if not is_tree(branch):
            return False
    return True

def is_leaf(tree):
    """Returns True if the given tree's list of branches is empty, and False
    otherwise.
    """
    return not branches(tree)

def print_tree(t, indent=0):
    """Print a representation of this tree in which each node is
    indented by two spaces times its depth from the root.

    >>> print_tree(tree(1))
    1
    >>> print_tree(tree(1, [tree(2)]))
    1
      2
    >>> numbers = tree(1, [tree(2), tree(3, [tree(4), tree(5)]), tree(6, [tree(7)])])
    >>> print_tree(numbers)
    1
      2
      3
        4
        5
      6
        7
    """
    print('  ' * indent + str(label(t)))
    for b in branches(t):
        print_tree(b, indent + 1)

def copy_tree(t):
    """Returns a copy of t. Only for testing purposes.

    >>> t = tree(5)
    >>> copy = copy_tree(t)
    >>> t = tree(6)
    >>> print_tree(copy)
    5
    """
    return tree(label(t), [copy_tree(b) for b in branches(t)])

我们要实现replace_leaf方法,将label值等于old的叶子节点,替换成new值。注意,只有叶子节点需要替换。

答案

很显然,我们需要使用递归来完成。在递归时,我们输入的节点有两种可能,一种是叶子节点,一种不是。如果是叶子结点,我们根据节点的值来判断是否需要替换,如果不是,那么进行递归。

代码语言:javascript
复制
def replace_leaf(t, old, new):
    """Returns a new tree where every leaf value equal to old has
    been replaced with new.

    >>> yggdrasil = tree('odin',
    ...                  [tree('balder',
    ...                        [tree('thor'),
    ...                         tree('loki')]),
    ...                   tree('frigg',
    ...                        [tree('thor')]),
    ...                   tree('thor',
    ...                        [tree('sif'),
    ...                         tree('thor')]),
    ...                   tree('thor')])
    >>> laerad = copy_tree(yggdrasil) # copy yggdrasil for testing purposes
    >>> print_tree(replace_leaf(yggdrasil, 'thor', 'freya'))
    odin
      balder
        freya
        loki
      frigg
        freya
      thor
        sif
        freya
      freya
    >>> laerad == yggdrasil # Make sure original tree is unmodified
    True
    """
    "*** YOUR CODE HERE ***"
    if is_leaf(t):
        val = new if label(t) == old else label(t)
        return tree(val)
    return tree(label(t), [replace_leaf(b, old, new) for b in branches(t)])

Q2: Towers of Hanoi

经典的汉诺塔问题,汉诺塔是如下图的一个游戏。它有三根木杆,其中一根木杆上从小到大堆叠了n个圆盘。现在我们需要将这些圆盘从当前木杆全部移动到另外一根木杆上去。

在移动的过程当中需要遵守一些规则:

  • 一次只能移动一块圆盘
  • 每次只能移动顶端的圆盘,并且只能放在其他木杆的顶部
  • 大的圆盘不能摆放在小的圆盘上

要求我们实现算法完成木杆的移动。

答案

这是一道经典的递归问题,我们分析问题可以发现,由于大的圆盘不能摆放在小的圆盘上。假设我们要将圆盘从A移动至C,那么我们一定要保证1到n-1块圆盘全部移动到了B上,否则最下层最大的圆盘无法放在C上。

假设我们通过某种方法f将n-1块圆盘移动到了B上,这时A上只剩下最下层的圆盘n,我们将它移动到C。之后,我们只需要重复方法f,将B上的圆盘移动到C即可。由于三根木杆在移动的规则中是等价的,所以只要再使用方法f,就可以完成整个移动过程。

其实,这就是一个经典的递归过程。对于move_stack函数来说,它的功能是将n块圆盘从start移动向end。我们假设除了startend之外的木杆叫做other,我们只需要调用move_stack就可以完成圆盘的移动。

代码语言:javascript
复制
def print_move(origin, destination):
    """Print instructions to move a disk."""
    print("Move the top disk from rod", origin, "to rod", destination)

def move_stack(n, start, end):
    """Print the moves required to move n disks on the start pole to the end
    pole without violating the rules of Towers of Hanoi.

    n -- number of disks
    start -- a pole position, either 1, 2, or 3
    end -- a pole position, either 1, 2, or 3

    There are exactly three poles, and start and end must be different. Assume
    that the start pole has at least n disks of increasing size, and the end
    pole is either empty or has a top disk larger than the top n start disks.

    >>> move_stack(1, 1, 3)
    Move the top disk from rod 1 to rod 3
    >>> move_stack(2, 1, 3)
    Move the top disk from rod 1 to rod 2
    Move the top disk from rod 1 to rod 3
    Move the top disk from rod 2 to rod 3
    >>> move_stack(3, 1, 3)
    Move the top disk from rod 1 to rod 3
    Move the top disk from rod 1 to rod 2
    Move the top disk from rod 3 to rod 2
    Move the top disk from rod 1 to rod 3
    Move the top disk from rod 2 to rod 1
    Move the top disk from rod 2 to rod 3
    Move the top disk from rod 1 to rod 3
    """
    assert 1 <= start <= 3 and 1 <= end <= 3 and start != end, "Bad start/end"
    "*** YOUR CODE HERE ***"
    if n == 1:
        print_move(start, end)
    else:
        other = 6 - start - end
        move_stack(n-1, start, other)
        print_move(start, end)
        move_stack(n-1, other, end)

mobile

mobile是美国的一种悬挂的装饰品,它有两边,大概长这个样子:

它的每一边都有一定的长度,可以挂上重物或者是另外一个mobile。

现在提供了一些代码,将mobile抽象成了树结构。

  • 一个mobile拥有左边side和右边side
  • 一个side拥有一个长度和一个结构,可以是一个mobile或者是一个weight
  • 一个weight拥有体积,是一个正整数
代码语言:javascript
复制
def mobile(left, right):
    """Construct a mobile from a left side and a right side."""
    return tree('mobile', [left, right])

def is_mobile(m):
    return is_tree(m) and label(m) == 'mobile'

def sides(m):
    """Select the sides of a mobile."""
    assert is_mobile(m), "must call sides on a mobile"
    return branches(m)

def is_side(m):
    return not is_mobile(m) and not is_weight(m) and type(label(m)) == int

Q3: Weights

实现weight的数据抽象,包括weight构造函数,size选择器和is_weight判断输入是否是一个weightweight是通过树结构表示的,total_weight代码是一个使用mobile,sideweight的例子。

代码语言:javascript
复制
def side(length, mobile_or_weight):
    """Construct a side: a length of rod with a mobile or weight at the end."""
    return tree(length, [mobile_or_weight])

def length(s):
    """Select the length of a side."""
    assert is_side(s), "must call length on a side"
    return label(s)

def end(s):
    """Select the mobile or weight hanging at the end of a side."""
    assert is_side(s), "must call end on a side"
    return branches(s)[0]
答案

可以认为weight是一种特殊的叶子节点,它存储的值是整数。

代码语言:javascript
复制
def weight(size):
    """Construct a weight of some size."""
    assert size > 0
    "*** YOUR CODE HERE ***"
    return tree(size, [])

def size(w):
    """Select the size of a weight."""
    "*** YOUR CODE HERE ***"
    assert is_weight(w)
    return label(w)

def is_weight(w):
    """Whether w is a weight, not a mobile."""
    "*** YOUR CODE HERE ***"
    return is_leaf(w) and type(label(w)) == int

Q4: Balanced

实现balanced函数,返回m是否平衡。对于mobile来说要取得平衡需要满足两个条件:

  • mobile左右两边的扭力(torque)相等,左边的扭力是左边的长度乘上左边的总重量,同理,右边的扭力是右边的长度乘上右边的总重
  • 每一个子mobile都是平衡的

提示:你可以假设所有的weight是平衡的

答案

对于mobile来说,可能出现在它挂钩上的只有三样。分别是mobilesideweight

其中weight我们假设它们是平衡的,由于size尾端还可能挂上其他书籍,因此size需要继续递归。最后是mobile,显然这种情况需要递归,那么我们递归调用balanced来解决。

在递归时,如果是mobile,首先我们需要保证递归调用处理的每一个子挂件都是平衡的,其次我们要保证当前mobile两侧的扭力是相等的。

难点主要在于梳理清楚递归过程当中可能遇到的所有情况。

代码语言:javascript
复制
def balanced(m):
    """Return whether m is balanced.

    >>> t, u, v = examples()
    >>> balanced(t)
    True
    >>> balanced(v)
    True
    >>> w = mobile(side(3, t), side(2, u))
    >>> balanced(w)
    False
    >>> balanced(mobile(side(1, v), side(1, w)))
    False
    >>> balanced(mobile(side(1, w), side(1, v)))
    False
    """
    "*** YOUR CODE HERE ***"
    if is_weight(m):
        return True
    if is_side(m):
        return balanced(end(m))
    
    flag = all([balanced(s) for s in sides(m)])
    left, right = sides(m)[0], sides(m)[1]
    left, right = length(left) * total_weight(end(left)), length(right) * total_weight(end(right))
    return left == right and flag

面向对象

Q5: Retirement

Account类实现time_retire的函数,Account类拥有一个参数amount,返回需要多少年,账户存款balance能够达到或超过amount。假设银行在每年年底派发利息,利息写死在了代码里。

答案

这题基本没有难度,按照题目要求实现逻辑即可。

可以使用循环来计算,但是当amount很大的时候,可能会比较慢。所以我们可以通过数学公式推导直接求。我们列出不等式:

balance * i ^ x >= amount

这里的i是利息,balance是余额,amount是退休需要的目标。我们移项化简之后,得:

i^x >= \frac {amount}{balance}

取log之后,变形为:

x >= \log(\frac{\frac{amount}{balance}}{\log i})
代码语言:javascript
复制
class Account:
    """An account has a balance and a holder.

    >>> a = Account('John')
    >>> a.deposit(10)
    10
    >>> a.balance
    10
    >>> a.interest
    0.02

    >>> a.time_to_retire(10.25) # 10 -> 10.2 -> 10.404
    2
    >>> a.balance               # balance should not change
    10
    >>> a.time_to_retire(11)    # 10 -> 10.2 -> ... -> 11.040808032
    5
    >>> a.time_to_retire(100)
    117
    """

    interest = 0.02  # A class attribute

    def __init__(self, account_holder):
        self.holder = account_holder
        self.balance = 0

    def deposit(self, amount):
        """Add amount to balance."""
        self.balance = self.balance + amount
        return self.balance

    def withdraw(self, amount):
        """Subtract amount from balance if funds are available."""
        if amount > self.balance:
            return 'Insufficient funds'
        self.balance = self.balance - amount
        return self.balance

    def time_to_retire(self, amount):
        """Return the number of years until balance would grow to amount."""
        assert self.balance > 0 and amount > 0 and self.interest > 0
        "*** YOUR CODE HERE ***"
        import math
        return math.ceil(math.log(amount / self.balance) / math.log(1 + Account.interest))

Q6: Free Checking

实现FreeChecking类,加入手续费功能。假设每次取款都需要支付手续费1,并且前两次取款申请时可以豁免手续费,但只能豁免两次,并且豁免次数是按照申请算的,只要申请了取款,即使取款失败一样会计入豁免次数。

答案

唯一的难点在于豁免次数free_withdrawals是写在类当中的,属于类变量。类变量是被所有类实例共享的,所以一旦某一个实例修改了它,对于其他实例一样会生效。

所以我们不能直接修改FreeChecking.free_withdrawals,而需要重构构造函数,将它拷贝一份放入实例当中。

代码语言:javascript
复制
class FreeChecking(Account):
    """A bank account that charges for withdrawals, but the first two are free!

    >>> ch = FreeChecking('Jack')
    >>> ch.balance = 20
    >>> ch.withdraw(100)  # First one's free
    'Insufficient funds'
    >>> ch.withdraw(3)    # And the second
    17
    >>> ch.balance
    17
    >>> ch.withdraw(3)    # Ok, two free withdrawals is enough
    13
    >>> ch.withdraw(3)
    9
    >>> ch2 = FreeChecking('John')
    >>> ch2.balance = 10
    >>> ch2.withdraw(3) # No fee
    7
    >>> ch.withdraw(3)  # ch still charges a fee
    5
    >>> ch.withdraw(5)  # Not enough to cover fee + withdraw
    'Insufficient funds'
    """
    withdraw_fee = 1
    free_withdrawals = 2

    "*** YOUR CODE HERE ***"
    def __init__(self, account_holder):
        self.holder = account_holder
        self.balance = 0
        self.free_withdrawals = FreeChecking.free_withdrawals

    def withdraw(self, amount):
        amount += self.withdraw_fee if self.free_withdrawals == 0 else 0
        if self.free_withdrawals > 0:
            self.free_withdrawals -= 1
        if self.balance < amount:
            return 'Insufficient funds'
        self.balance -= amount
        return self.balance

Mutation

实现函数make_counter,它会返回一个函数countercounter会统计每一个字符串出现的次数。从第一次出现开始计。

答案

函数式编程闭包的常规用法,我们创建一个字典存储每一个字符串出现的次数即可。

代码语言:javascript
复制
def make_counter():
    """Return a counter function.

    >>> c = make_counter()
    >>> c('a')
    1
    >>> c('a')
    2
    >>> c('b')
    1
    >>> c('a')
    3
    >>> c2 = make_counter()
    >>> c2('b')
    1
    >>> c2('b')
    2
    >>> c('b') + c2('b')
    5
    """
    "*** YOUR CODE HERE ***"
    dt = {}
    def process(c):
        ret = dt.get(c, 1)
        dt[c] = ret + 1
        return ret
    return process

Q8: Next Fibonacci

实现一个函数make_fib返回一个函数,调用这个函数时会自动返回下一个斐波那契数列。

斐波那契数列从0开始,第二个数是1,从第三个数起每个数是前两个数之和。

提示:我们可以使用nonlocal修改外层scope中定义的变量

答案

我们把当前数和前一个数定义在process函数外围,每次调用时更新。

代码语言:javascript
复制
def make_fib():
    """Returns a function that returns the next Fibonacci number
    every time it is called.

    >>> fib = make_fib()
    >>> fib()
    0
    >>> fib()
    1
    >>> fib()
    1
    >>> fib()
    2
    >>> fib()
    3
    >>> fib2 = make_fib()
    >>> fib() + sum([fib2() for _ in range(5)])
    12
    """
    "*** YOUR CODE HERE ***"
    prev, cur = 1, 0
    def process():
        nonlocal prev, cur
        ret = cur
        prev, cur = cur, prev + cur
        return ret
    return process

Q9: Password Protected Account

我们可以通过函数创建可变变量,比如说我们有一个函数make_withdraw,它可以实现从账号中取钱的功能:

代码语言:javascript
复制
def make_withdraw(balance):
    """Return a withdraw function with BALANCE as its starting balance.
    >>> withdraw = make_withdraw(1000)
    >>> withdraw(100)
    900
    >>> withdraw(100)
    800
    >>> withdraw(900)
    'Insufficient funds'
    """
    def withdraw(amount):
        nonlocal balance
        if amount > balance:
           return 'Insufficient funds'
        balance = balance - amount
        return balance
    return withdraw

现在我们需要对这个函数进行改进,添加密码验证的功能。新版的make_withdraw接收两个参数:账户余额balance和初始密码password。返回结果是一个函数withdraw,它接收两个参数amount表示取钱金额,password输入的密码。

只有当输入密码和初试密码匹配时才能取款成功,否则的话视作失败,并且将错误密码存入list中,返回错误信息:"Incorrect password"

当错误输入密码三次时锁定账号,所有操作都会返回:

代码语言:javascript
复制
"Your account is locked. Attempts: [<p1>, <p2>, <p3>]"

P1, p2, p3为之前输错的密码。

答案

也是闭包的常规用法, 只不过我们要在子函数中修改外层的balance,所以需要使用关键字nonlocal

由于需要存储所有输错的密码,所以还需要在make_withdraw函数中定义一个list用来存储出错的密码。由于list本身是可变类型,我们往当中插入元素,不会改变对象本身,所以可以不用加nonlocal关键字修饰。

代码语言:javascript
复制
def make_withdraw(balance, password):
    """Return a password-protected withdraw function.

    >>> w = make_withdraw(100, 'hax0r')
    >>> w(25, 'hax0r')
    75
    >>> error = w(90, 'hax0r')
    >>> error
    'Insufficient funds'
    >>> error = w(25, 'hwat')
    >>> error
    'Incorrect password'
    >>> new_bal = w(25, 'hax0r')
    >>> new_bal
    50
    >>> w(75, 'a')
    'Incorrect password'
    >>> w(10, 'hax0r')
    40
    >>> w(20, 'n00b')
    'Incorrect password'
    >>> w(10, 'hax0r')
    "Your account is locked. Attempts: ['hwat', 'a', 'n00b']"
    >>> w(10, 'l33t')
    "Your account is locked. Attempts: ['hwat', 'a', 'n00b']"
    >>> type(w(10, 'l33t')) == str
    True
    """
    "*** YOUR CODE HERE ***"
    tried = []
    def withdraw(amount, passw):
        nonlocal balance
        if len(tried) == 3:
            return "Your account is locked. Attempts: ['{}', '{}', '{}'']".format(tried[0], tried[1], tried[2])
        if passw != password:
            tried.append(passw)
            return 'Incorrect password'
        if amount > balance:
            return 'Insufficient funds'
        balance -= amount
        return balance
    return withdraw

Q10: Joint Account

现在需要我们开发账号关联功能,实现一个函数make_joint,它接收三个输入:

  • 一个有密码保护的withdraw函数
  • withdraw函数的原始密码
  • 关联withdraw账号的新密码

make_joint返回withdraw函数,它既可以通过原始密码取款,也可以通过新关联的新密码取款。两种方式操作到同一个账户上。无论是make_joint还是withdraw密码错误都会被记录,并且三次之后会触发账号锁定。

提示:

  • 实现逻辑很短,不超过10行代码,并且不包含字符串处理
  • 关键是使用正确的amount和密码调用现成的withdraw函数
  • 你可以假设withdraw调用失败返回类型是string,否则是int

可以使用type(value) == str来判断value的类型是不是string。

答案

由于make_joint时传入的old_password不一定正确,我们可以执行一次取款0元的操作来验证ret = withdraw(0, old_password)

如果返回值类型是字符串,那么说明密码错误,否则说明密码正确。

难点在于当make_joint重复调用嵌套的时候,需要保证所有后来关联的新密码都可以使用。

这里可以利用dict来存储新旧密码之间的映射关系,即使嵌套也可以work。我们来看下这个样例:

代码语言:javascript
复制
j = make_joint(w, 'hax0r', 'secret')
j2 = make_joint(j, 'secret', 'code')

我们在创建j2函数时,传入的withdraw参数其实是j。在调用j2进行取款时,会首先将密码'code'映射成'secret'。接着调用j函数,再将'secret'映射成'hax0r'取款成功。

代码语言:javascript
复制
def make_joint(withdraw, old_password, new_password):
    """Return a password-protected withdraw function that has joint access to
    the balance of withdraw.

    >>> w = make_withdraw(100, 'hax0r')
    >>> w(25, 'hax0r')
    75
    >>> make_joint(w, 'my', 'secret')
    'Incorrect password'
    >>> j = make_joint(w, 'hax0r', 'secret')
    >>> w(25, 'secret')
    'Incorrect password'
    >>> j(25, 'secret')
    50
    >>> j(25, 'hax0r')
    25
    >>> j(100, 'secret')
    'Insufficient funds'

    >>> j2 = make_joint(j, 'secret', 'code')
    >>> j2(5, 'code')
    20
    >>> j2(5, 'secret')
    15
    >>> j2(5, 'hax0r')
    10

    >>> j2(25, 'password')
    'Incorrect password'
    >>> j2(5, 'secret')
    "Your account is locked. Attempts: ['my', 'secret', 'password']"
    >>> j(5, 'secret')
    "Your account is locked. Attempts: ['my', 'secret', 'password']"
    >>> w(5, 'hax0r')
    "Your account is locked. Attempts: ['my', 'secret', 'password']"
    >>> make_joint(w, 'hax0r', 'hello')
    "Your account is locked. Attempts: ['my', 'secret', 'password']"
    """
    "*** YOUR CODE HERE ***"
    ret = withdraw(0, old_password)
    passw = {}
    if type(ret) == int:
        passw[new_password] = old_password
        def process(amount, password):
            return withdraw(amount, passw.get(password, password))
        return process
    else:
        return ret

附加题

Interval

Alyssa P 发明了一种区间计算的方法,对区间加减乘除的结果都是区间。这里的区间定义为[lower_bound, upper_bound]

比如两个区间[a, b][l, r]相加,得到的结果就是[a+l, b+r]。这是因为从两个区间任意选两个点相加,得到的最小结果是a+l,最大结果是b+r

代码语言:javascript
复制
def str_interval(x):
    """Return a string representation of interval x."""
    return '{0} to {1}'.format(lower_bound(x), upper_bound(x))

def add_interval(x, y):
    """Return an interval that contains the sum of any value in interval x and
    any value in interval y."""
    lower = lower_bound(x) + lower_bound(y)
    upper = upper_bound(x) + upper_bound(y)
    return interval(lower, upper)

Q11: Interval Abstraction

实现区间的抽象函数,这没有难度:

代码语言:javascript
复制
def interval(a, b):
    """Construct an interval from a to b."""
    return [a, b]

def lower_bound(x):
    """Return the lower bound of interval x."""
    "*** YOUR CODE HERE ***"
    return x[0]

def upper_bound(x):
    """Return the upper bound of interval x."""
    "*** YOUR CODE HERE ***"
    return x[1]

区间相乘的计算方法如下:

代码语言:javascript
复制
def mul_interval(x, y):
    """Return the interval that contains the product of any value in x and any
    value in y."""
    p1 = x[0] * y[0]
    p2 = x[0] * y[1]
    p3 = x[1] * y[0]
    p4 = x[1] * y[1]
    return [min(p1, p2, p3, p4), max(p1, p2, p3, p4)]

一样是遵循从两个区间分别取点相乘,求最小和最大的思路。

Q12: Sub Interval

实现两个区间相减,x左端点减去y右端点可以得到最小值,x右端点减去y左端点得到最大值。

代码语言:javascript
复制
def sub_interval(x, y):
    """Return the interval that contains the difference between any value in x
    and any value in y."""
    "*** YOUR CODE HERE ***"
    lower = lower_bound(x) - upper_bound(y)
    upper = upper_bound(x) - lower_bound(y)
    return interval(lower, upper)

Q13: Div Interval

实现区间相除,代码已经实现,需要我们加上断言,防止除以0的情况出现,即除数y区间不能包含0,即y区间的左右端点不能异号。

代码语言:javascript
复制
def div_interval(x, y):
    """Return the interval that contains the quotient of any value in x divided by
    any value in y. Division is implemented as the multiplication of x by the
    reciprocal of y."""
    "*** YOUR CODE HERE ***"
    assert (lower_bound(y) * upper_bound(y)) > 0, 'can divide zero'
    reciprocal_y = interval(1/upper_bound(y), 1/lower_bound(y))
    return mul_interval(x, reciprocal_y)

Q14: Par Diff

在Alyssa P完成了这份工作之后的很多年,有一个叫Lem E. Tweakit的人注意到并联电路电阻有两种表达方式:

代码语言:javascript
复制
par1(r1, r2) = (r1 * r2) / (r1 + r2)
par2(r1, r2) = 1 / (1/r1 + 1/r2)

他通过编码,将这两种计算方式都实现了:

代码语言:javascript
复制
def par1(r1, r2):
    return div_interval(mul_interval(r1, r2), add_interval(r1, r2))

def par2(r1, r2):
    one = interval(1, 1)
    rep_r1 = div_interval(one, r1)
    rep_r2 = div_interval(one, r2)
    return div_interval(one, add_interval(rep_r1, rep_r2))

但是他在实验的时候发现,虽然代数上这两个公式是等价的,但是使用Alyssa P规则对区间进行运算之后得到的结果不同。

现在请举一个反例证明Lem是正确的。

答案

非常简单,很容易构造。

代码语言:javascript
复制
def check_par():
    """Return two intervals that give different results for parallel resistors.

    >>> r1, r2 = check_par()
    >>> x = par1(r1, r2)
    >>> y = par2(r1, r2)
    >>> lower_bound(x) != lower_bound(y) or upper_bound(x) != upper_bound(y)
    True
    """
    r1 = interval(2, 4) # Replace this line!
    r2 = interval(2, 4) # Replace this line!
    return r1, r2

Q15: Multiple References

Eva Lu Ator,另外一个使用者,她发现使用区间计算法则时哪怕是代数上等价的式子,在实际上计算的时候,得到的结果却是不一样的。这时由于对不确定区间多重引用导致的。

多重引用问题(Multiple references problem):对于代数上等价的公式,在使用区间计算法则的时候,表示不确定区间的变量重复引用得越少,最后得到的结果范围也越小。比如par1中r1 * r2r1 + r2(r1 * r2) / (r1 + r2)这三处运算都是对不确定区间的多重引用。

由此,她认为par2是计算并联电阻更好的方式。她说的对吗,请给出说明或解释。

答案

Eva是正确的,问题在于,当我们对同一个区间进行计算的时候,从两个区间中选出的值是不同的,这会导致得到的结果比实际的值要更大。比如当我们计算[-1, 1]计算平方时,得到的结果是[-1, 1],但实际上在实数集不可能存在一个数的平方小于0。

因此par2是更好的计算方法,在par2中与不确定的区间r1、r2计算的都是固定的区间[1, 1],所以不会有重复引用问题。

Q16: Quadratic

实现作用于区间的二次函数

f(x)=ax^2 + bx + c

提示:二次函数的导数为

f'(x) = 2ax + b

,极值点为-b/(2a)

答案

我们要做的事情很简单,就是遍历区间中的每一个点,找到二次函数对应的最小值和最大值。

但问题是我们不可能遍历完所有的点,因为小数是无穷无尽的。好在二次函数的增减情况是固定的,我们只需要求出极值点,根据极值点是否在区间里有两种不同的计算方式。

如果极值点在区间里,那么我们只需要分别求出左右端点和极值点对应的函数值,找出其中的最大和最小就是答案。

如果极值点不在区间,那么区间要么递增要么递减,我们只需要求出两个端点的值,找出最大和最小即可。

只要熟悉二次函数的增减性,不难求解。

代码语言:javascript
复制
def quadratic(x, a, b, c):
    """Return the interval that is the range of the quadratic defined by
    coefficients a, b, and c, for domain interval x.

    >>> str_interval(quadratic(interval(0, 2), -2, 3, -1))
    '-3 to 0.125'
    >>> str_interval(quadratic(interval(1, 3), 2, -3, 1))
    '0 to 10'
    """
    "*** YOUR CODE HERE ***"
    f = lambda x: a * x * x + b * x + c
    extreme_point = -b / (2 * a)
    lower = lower_bound(x)
    upper = upper_bound(x)
    if lower <= extreme_point <= upper:
        left, mid, right = f(lower), f(extreme_point), f(upper)
        return interval(min(left, right, mid), max(left, right, mid))
    else:
        left, right = f(lower), f(upper)
        return interval(min(left, right), max(left, right))

这一次作业的量还是挺大的,十几道题前前后后做了我两三个小时。虽然看着我写的简单,但真要不看答案全靠自己思考和调试做下来还是很锻炼人的。这些题的质量很高,难度不错,不像之前的题目那么难,但也不算简单,基本上每道题都需要好好思考才能做得出来。

希望大家好好做题,好好学习,虽然上不了伯克利,但至少能把伯克利的知识学到,你说是吧?

好了,关于这次的作业就聊到这里,感谢大家的阅读。

喜欢本文的话不要忘记三连~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coder梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Q1: Replace Leaf
    • 答案
  • Q2: Towers of Hanoi
    • 答案
  • mobile
    • Q3: Weights
      • 答案
    • Q4: Balanced
      • 答案
  • 面向对象
    • Q5: Retirement
      • 答案
    • Q6: Free Checking
      • 答案
  • Mutation
    • 答案
    • Q8: Next Fibonacci
      • 答案
    • Q9: Password Protected Account
      • 答案
    • Q10: Joint Account
      • 答案
  • 附加题
    • Interval
    • Q11: Interval Abstraction
    • Q12: Sub Interval
    • Q13: Div Interval
    • Q14: Par Diff
      • 答案
    • Q15: Multiple References
      • 答案
    • Q16: Quadratic
      • 答案
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档