作者 | 梁唐
出品 | 公众号:Coder梁(ID:Coder_LT)
大家好,日拱一卒,我是梁唐。
我们继续来看伯克利公开课CS61A,这一次我们一起来做一下作业5,原链接:https://inst.eecs.berkeley.edu//~cs61a/sp18/hw/hw05/#mobiles
这一次的作业是一个双周作业,因此题量比较大,一共有17题。除了最后一道题之外,难度还可以,非常锻炼人,但比之前的一些难题要稍稍简单一些。所以我决定分成两篇写完,最后一题单独一篇,剩余的16题一篇。
这一次的题目同样关于函数式编程以及递归,加入了一些面向对象设计的部分。
基本上可以这么说,只要认认真真不看答案把这些题做完,基本上对于递归以及Python函数式编程的理解就算是初出茅庐了。
好了,废话不多说,我们直接开始看题吧。
公开课里老师讲解了通过Python实现树结构的方式,如果大家没有看过对应的课程也没有关系,其实只是使用递归对树结构进行定义。它的核心只有几个函数,代码如下:
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值。注意,只有叶子节点需要替换。
很显然,我们需要使用递归来完成。在递归时,我们输入的节点有两种可能,一种是叶子节点,一种不是。如果是叶子结点,我们根据节点的值来判断是否需要替换,如果不是,那么进行递归。
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)])
经典的汉诺塔问题,汉诺塔是如下图的一个游戏。它有三根木杆,其中一根木杆上从小到大堆叠了n个圆盘。现在我们需要将这些圆盘从当前木杆全部移动到另外一根木杆上去。

在移动的过程当中需要遵守一些规则:
要求我们实现算法完成木杆的移动。
这是一道经典的递归问题,我们分析问题可以发现,由于大的圆盘不能摆放在小的圆盘上。假设我们要将圆盘从A移动至C,那么我们一定要保证1到n-1块圆盘全部移动到了B上,否则最下层最大的圆盘无法放在C上。
假设我们通过某种方法f将n-1块圆盘移动到了B上,这时A上只剩下最下层的圆盘n,我们将它移动到C。之后,我们只需要重复方法f,将B上的圆盘移动到C即可。由于三根木杆在移动的规则中是等价的,所以只要再使用方法f,就可以完成整个移动过程。
其实,这就是一个经典的递归过程。对于move_stack函数来说,它的功能是将n块圆盘从start移动向end。我们假设除了start和end之外的木杆叫做other,我们只需要调用move_stack就可以完成圆盘的移动。
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或者是一个weightweight拥有体积,是一个正整数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
实现weight的数据抽象,包括weight构造函数,size选择器和is_weight判断输入是否是一个weight。weight是通过树结构表示的,total_weight代码是一个使用mobile,side和weight的例子。
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是一种特殊的叶子节点,它存储的值是整数。
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
实现balanced函数,返回m是否平衡。对于mobile来说要取得平衡需要满足两个条件:
mobile左右两边的扭力(torque)相等,左边的扭力是左边的长度乘上左边的总重量,同理,右边的扭力是右边的长度乘上右边的总重mobile都是平衡的提示:你可以假设所有的
weight是平衡的
对于mobile来说,可能出现在它挂钩上的只有三样。分别是mobile,side和weight。
其中weight我们假设它们是平衡的,由于size尾端还可能挂上其他书籍,因此size需要继续递归。最后是mobile,显然这种情况需要递归,那么我们递归调用balanced来解决。
在递归时,如果是mobile,首先我们需要保证递归调用处理的每一个子挂件都是平衡的,其次我们要保证当前mobile两侧的扭力是相等的。
难点主要在于梳理清楚递归过程当中可能遇到的所有情况。
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
为Account类实现time_retire的函数,Account类拥有一个参数amount,返回需要多少年,账户存款balance能够达到或超过amount。假设银行在每年年底派发利息,利息写死在了代码里。
这题基本没有难度,按照题目要求实现逻辑即可。
可以使用循环来计算,但是当amount很大的时候,可能会比较慢。所以我们可以通过数学公式推导直接求。我们列出不等式:
这里的i是利息,balance是余额,amount是退休需要的目标。我们移项化简之后,得:
取log之后,变形为:
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))
实现FreeChecking类,加入手续费功能。假设每次取款都需要支付手续费1,并且前两次取款申请时可以豁免手续费,但只能豁免两次,并且豁免次数是按照申请算的,只要申请了取款,即使取款失败一样会计入豁免次数。
唯一的难点在于豁免次数free_withdrawals是写在类当中的,属于类变量。类变量是被所有类实例共享的,所以一旦某一个实例修改了它,对于其他实例一样会生效。
所以我们不能直接修改FreeChecking.free_withdrawals,而需要重构构造函数,将它拷贝一份放入实例当中。
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
实现函数make_counter,它会返回一个函数counter,counter会统计每一个字符串出现的次数。从第一次出现开始计。
函数式编程闭包的常规用法,我们创建一个字典存储每一个字符串出现的次数即可。
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
实现一个函数make_fib返回一个函数,调用这个函数时会自动返回下一个斐波那契数列。
斐波那契数列从0开始,第二个数是1,从第三个数起每个数是前两个数之和。
提示:我们可以使用nonlocal修改外层scope中定义的变量
我们把当前数和前一个数定义在process函数外围,每次调用时更新。
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
我们可以通过函数创建可变变量,比如说我们有一个函数make_withdraw,它可以实现从账号中取钱的功能:
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"
当错误输入密码三次时锁定账号,所有操作都会返回:
"Your account is locked. Attempts: [<p1>, <p2>, <p3>]"
P1, p2, p3为之前输错的密码。
也是闭包的常规用法, 只不过我们要在子函数中修改外层的balance,所以需要使用关键字nonlocal。
由于需要存储所有输错的密码,所以还需要在make_withdraw函数中定义一个list用来存储出错的密码。由于list本身是可变类型,我们往当中插入元素,不会改变对象本身,所以可以不用加nonlocal关键字修饰。
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
现在需要我们开发账号关联功能,实现一个函数make_joint,它接收三个输入:
withdraw函数withdraw函数的原始密码withdraw账号的新密码make_joint返回withdraw函数,它既可以通过原始密码取款,也可以通过新关联的新密码取款。两种方式操作到同一个账户上。无论是make_joint还是withdraw密码错误都会被记录,并且三次之后会触发账号锁定。
提示:
withdraw函数withdraw调用失败返回类型是string,否则是int可以使用type(value) == str来判断value的类型是不是string。
由于make_joint时传入的old_password不一定正确,我们可以执行一次取款0元的操作来验证ret = withdraw(0, old_password)。
如果返回值类型是字符串,那么说明密码错误,否则说明密码正确。
难点在于当make_joint重复调用嵌套的时候,需要保证所有后来关联的新密码都可以使用。
这里可以利用dict来存储新旧密码之间的映射关系,即使嵌套也可以work。我们来看下这个样例:
j = make_joint(w, 'hax0r', 'secret')
j2 = make_joint(j, 'secret', 'code')
我们在创建j2函数时,传入的withdraw参数其实是j。在调用j2进行取款时,会首先将密码'code'映射成'secret'。接着调用j函数,再将'secret'映射成'hax0r'取款成功。
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
Alyssa P 发明了一种区间计算的方法,对区间加减乘除的结果都是区间。这里的区间定义为[lower_bound, upper_bound]。
比如两个区间[a, b]和[l, r]相加,得到的结果就是[a+l, b+r]。这是因为从两个区间任意选两个点相加,得到的最小结果是a+l,最大结果是b+r。
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)
实现区间的抽象函数,这没有难度:
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]
区间相乘的计算方法如下:
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)]
一样是遵循从两个区间分别取点相乘,求最小和最大的思路。
实现两个区间相减,x左端点减去y右端点可以得到最小值,x右端点减去y左端点得到最大值。
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)
实现区间相除,代码已经实现,需要我们加上断言,防止除以0的情况出现,即除数y区间不能包含0,即y区间的左右端点不能异号。
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)
在Alyssa P完成了这份工作之后的很多年,有一个叫Lem E. Tweakit的人注意到并联电路电阻有两种表达方式:
par1(r1, r2) = (r1 * r2) / (r1 + r2)
par2(r1, r2) = 1 / (1/r1 + 1/r2)
他通过编码,将这两种计算方式都实现了:
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是正确的。
非常简单,很容易构造。
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
Eva Lu Ator,另外一个使用者,她发现使用区间计算法则时哪怕是代数上等价的式子,在实际上计算的时候,得到的结果却是不一样的。这时由于对不确定区间多重引用导致的。
多重引用问题(Multiple references problem):对于代数上等价的公式,在使用区间计算法则的时候,表示不确定区间的变量重复引用得越少,最后得到的结果范围也越小。比如par1中r1 * r2,r1 + r2,(r1 * r2) / (r1 + r2)这三处运算都是对不确定区间的多重引用。
由此,她认为par2是计算并联电阻更好的方式。她说的对吗,请给出说明或解释。
Eva是正确的,问题在于,当我们对同一个区间进行计算的时候,从两个区间中选出的值是不同的,这会导致得到的结果比实际的值要更大。比如当我们计算[-1, 1]计算平方时,得到的结果是[-1, 1],但实际上在实数集不可能存在一个数的平方小于0。
因此par2是更好的计算方法,在par2中与不确定的区间r1、r2计算的都是固定的区间[1, 1],所以不会有重复引用问题。
实现作用于区间的二次函数
提示:二次函数的导数为
,极值点为-b/(2a)
我们要做的事情很简单,就是遍历区间中的每一个点,找到二次函数对应的最小值和最大值。
但问题是我们不可能遍历完所有的点,因为小数是无穷无尽的。好在二次函数的增减情况是固定的,我们只需要求出极值点,根据极值点是否在区间里有两种不同的计算方式。
如果极值点在区间里,那么我们只需要分别求出左右端点和极值点对应的函数值,找出其中的最大和最小就是答案。
如果极值点不在区间,那么区间要么递增要么递减,我们只需要求出两个端点的值,找出最大和最小即可。
只要熟悉二次函数的增减性,不难求解。
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))
这一次作业的量还是挺大的,十几道题前前后后做了我两三个小时。虽然看着我写的简单,但真要不看答案全靠自己思考和调试做下来还是很锻炼人的。这些题的质量很高,难度不错,不像之前的题目那么难,但也不算简单,基本上每道题都需要好好思考才能做得出来。
希望大家好好做题,好好学习,虽然上不了伯克利,但至少能把伯克利的知识学到,你说是吧?
好了,关于这次的作业就聊到这里,感谢大家的阅读。
喜欢本文的话不要忘记三连~