家庭作业 2:递归、树递归、序列与列表
截止时间:7 月 8 日(星期三)晚上 11:59
指示
下载 hw02.zip的单行调用。
提交: 完成后,请将作业提交到 Gradescope。截止时间前可以多次提交,只有最后一次提交会被评分。请确认代码已成功提交;具体步骤参见 实验 0 ,其中有更详细的作业提交说明。
使用 Ok: 如果对 Ok 的使用有任何疑问,请参阅 这份指南。
阅读资源: 您可能会发现以下参考资料很有用:
评分: 作业根据正确性评分。每个不正确的问题将使总分减少一分。 此作业满分为 2 分。
题目要求
递归
问题 1:数字 8 的个数
编写递归函数 num_eights ,接收正整数 num ,返回其中数字 8 出现的次数。 num的单行调用。
Important: 必须使用递归;如果使用赋值语句或循环,测试将失败。(可以定义新函数,但其中也不能出现赋值语句。)
def num_eights(num: int) -> int:
"""Returns the number of times 8 appears as a digit of num.
>>> num_eights(3)
0
>>> num_eights(8)
1
>>> num_eights(88888888)
8
>>> num_eights(2638)
1
>>> num_eights(86380)
2
>>> num_eights(12345)
0
>>> num_eights(8782089)
3
>>> from construct_check import check
>>> # ban all assignment statements
>>> check(SOURCE_FILE, 'num_eights',
... ['Assign', 'AnnAssign', 'AugAssign', 'NamedExpr', 'For', 'While'])
True
"""
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q num_eights
问题 2:数字距离
对于一个整数, 数字距离 是相邻数字之差的绝对值之和。例如:
- 下面这个数的数字距离
61为5,因为要计算的绝对值为6 - 1为5的单行调用。 - 下面这个数的数字距离
71253为12(abs(7-1) + abs(1-2) + abs(2-5) + abs(5-3)=6 + 1 + 3 + 2). - 下面这个数的数字距离
6为0,因为它没有相邻数字对。
编写函数,计算正整数的数字距离。必须使用递归,否则测试会失败。
def digit_distance(num: int) -> int:
"""Determines the digit distance of num.
>>> digit_distance(3)
0
>>> digit_distance(777) # 0 + 0
0
>>> digit_distance(314) # 2 + 3
5
>>> digit_distance(31415926535) # 2 + 3 + 3 + 4 + ... + 2
32
>>> digit_distance(3464660003) # 1 + 2 + 2 + 2 + ... + 3
16
>>> from construct_check import check
>>> # ban all loops
>>> check(SOURCE_FILE, 'digit_distance',
... ['For', 'While'])
True
"""
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q digit_distance
问题 3:交错求和
编写函数 interleaved_sum,它接收数字 num 以及两个单参数函数: f_odd and f_even。它对从 1 到给定数字的奇数应用 f_odd
,对偶数应用 f_even ,并返回所有结果之和;范围包含终点 num 。的单行调用。
例如,执行 interleaved_sum(5, lambda x: x, lambda x: x * x)
会返回 1 + 2*2 + 3 + 4*4 + 5 = 29的单行调用。
Important: 实现时不能使用循环,也不能直接判断一个数是奇数还是偶数(不能使用
%)。不要直接检查奇偶性;从已知为奇数的 1 开始。Hint: 可以定义一个内部辅助函数,接收奇数
k并计算从该数到k的交错和num(包含num)。也可以使用相互递归。
def interleaved_sum(num: int, f_odd, f_even) -> int:
"""Compute the sum f_odd(1) + f_even(2) + f_odd(3) + ..., up
to num.
>>> identity = lambda x: x
>>> square = lambda x: x * x
>>> triple = lambda x: x * 3
>>> interleaved_sum(5, identity, square) # 1 + 2*2 + 3 + 4*4 + 5
29
>>> interleaved_sum(5, square, identity) # 1*1 + 2 + 3*3 + 4 + 5*5
41
>>> interleaved_sum(4, triple, square) # 1*3 + 2*2 + 3*3 + 4*4
32
>>> interleaved_sum(4, square, triple) # 1*1 + 2*3 + 3*3 + 4*3
28
>>> from construct_check import check
>>> check(SOURCE_FILE, 'interleaved_sum', ['While', 'For', 'Mod']) # ban loops and %
True
>>> check(SOURCE_FILE, 'interleaved_sum', ['BitAnd', 'BitOr', 'BitXor']) # ban bitwise operators, don't worry about these if you don't know what they are
True
"""
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q interleaved_sum
树递归
问题 4:美元找零计数
给定正整数 sum_needed,如果一组美元纸币的面值之和等于 sum_needed ,就称这组纸币能够为它找零 sum_needed。这里使用标准美元纸币面值:1、5、10、20、50 和 100。例如,下面这些组合都能为给定金额找零: 15:
- 15 张 1 美元纸币
- 10 张 1 美元和 1 张 5 美元纸币
- 5 张 1 美元和 2 张 5 美元纸币
- 5 张 1 美元和 1 张 10 美元纸币
- 3 张 5 美元纸币
- 1 张 5 美元和 1 张 10 美元纸币
因此,该金额共有 6 种找零方式: 15。编写 递归 函数
count_dollars ,接收正整数 sum_needed ,返回使用指定纸币面值为 sum_needed 找零的方法数。
请在答案中使用 next_smaller_dollar :
next_smaller_dollar 返回比输入面值小一级的纸币面值(例如 next_smaller_dollar(5) 为 1).
如果不存在更小面值,函数返回 None 。
Important: 必须使用递归;如果使用循环,测试会失败。
Hint: 可以参考 实现 of
count_partitions,了解如何用较小部分凑出目标值并统计方法数。如果递归调用之间需要记录多个值,可以考虑编写辅助函数。
def next_smaller_dollar(bill: int) -> int:
"""Returns the next smaller bill in order."""
if bill == 100:
return 50
if bill == 50:
return 20
if bill == 20:
return 10
elif bill == 10:
return 5
elif bill == 5:
return 1
def count_dollars(sum_needed: int) -> int:
"""Return the number of ways to make change.
>>> count_dollars(15) # 15 $1 bills, 10 $1 & 1 $5 bills, ... 1 $5 & 1 $10 bills
6
>>> count_dollars(10) # 10 $1 bills, 5 $1 & 1 $5 bills, 2 $5 bills, 10 $1 bills
4
>>> count_dollars(20) # 20 $1 bills, 15 $1 & $5 bills, ... 1 $20 bill
10
>>> count_dollars(45) # How many ways to make change for 45 dollars?
44
>>> count_dollars(100) # How many ways to make change for 100 dollars?
344
>>> count_dollars(200) # How many ways to make change for 200 dollars?
3274
>>> from construct_check import check
>>> # ban iteration
>>> check(SOURCE_FILE, 'count_dollars', ['While', 'For'])
True
"""
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q count_dollars
序列
问题 5:交错洗牌
Implement shuffle,它接收一个元素个数为偶数的序列 s (例如列表或 range),返回一个新列表,把原序列前半部分与后半部分的元素 交错排列 。该函数不会修改 s 。 s的单行调用。
所谓 交错排列 两个序列 s0 and s1 ,就是新列表依次包含第一个序列的第一个元素、 s0第二个序列的第一个元素、 s1第一个序列的第二个元素
s0第一个序列的第二个元素 s1,依此类推。例如,如果 s = [1, 2, 3, 4, 5, 6] ,那么前半部分是 s0 = [1, 2, 3]
,后半部分是 s1 = [4, 5, 6],交错排列后得到 s0 and s1 。
[1, 4, 2, 5, 3, 6]的单行调用。
def shuffle(s: list) -> list:
"""Return a shuffled list that interleaves the two halves of s.
>>> shuffle(range(6))
[0, 3, 1, 4, 2, 5]
>>> letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> shuffle(letters)
['a', 'e', 'b', 'f', 'c', 'g', 'd', 'h']
>>> shuffle(shuffle(letters))
['a', 'c', 'e', 'g', 'b', 'd', 'f', 'h']
>>> letters # Original list should not be modified
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
"""
assert len(s) % 2 == 0, 'len(seq) must be even'
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q shuffle
问题 6:深层映射
定义: A 数字嵌套列表 是包含数字和列表的列表。它可以只含数字、只含列表,或两者混合;其中的列表也必须是 数字嵌套列表。例如: [1, [2, [3]], 4], [1, 2,
3]以及 [[1, 2], [3, 4]] 都是数字嵌套列表。 数字嵌套列表的单行调用。
编写函数 deep_map ,接收两个参数:数字嵌套列表
s 和单参数函数 f。它会就地修改 s ,把 s 中的每个数字替换为对该数字调用 f 所得的结果。
Important:
deep_map会返回None,并且不应创建任何新列表。
Hint:
type(a) == list的求值结果是Trueifa是列表。
def deep_map(f, s: list) -> list:
"""Replace all non-list elements x with f(x) in the nested list s.
>>> six = [1, 2, [3, [4], 5], 6]
>>> deep_map(lambda x: x * x, six)
>>> six
[1, 4, [9, [16], 25], 36]
>>> # Check that you're not making new lists
>>> s = [3, [1, [4, [1]]]]
>>> s1 = s[1]
>>> s2 = s1[1]
>>> s3 = s2[1]
>>> deep_map(lambda x: x + 1, s)
>>> s
[4, [2, [5, [2]]]]
>>> s1 is s[1]
True
>>> s2 is s1[1]
True
>>> s3 is s2[1]
True
"""
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q deep_map
在本地检查你的分数
你可以运行以下命令来本地查看此作业的得分情况:
python3 ok --score
这不会提交作业! 当你对得分感到满意时,请将作业提交到 Gradescope 以获取学分。
提交作业
请上传所有你修改过的文件,将本次作业提交到 对应的 Gradescope 作业入口。 实验 00 中提供了详细说明。
可选题
以下题目为选做题。即使不完成,你仍能获得本次作业的学分;不过它们很适合用来练习,建议还是做一做!
问题 7:向上计算美元找零
Write a 递归 函数 count_dollars_upward ,它与 count_dollars
相同,只是改用 next_larger_dollar;该函数返回比输入面值大一级的纸币面值(例如 next_larger_dollar(5) 为 10).
如果不存在更小面值,函数返回 None 。
Important: 必须使用递归;如果使用循环,测试会失败。
def next_larger_dollar(bill: int) -> int:
"""Returns the next larger bill in order."""
if bill == 1:
return 5
elif bill == 5:
return 10
elif bill == 10:
return 20
elif bill == 20:
return 50
elif bill == 50:
return 100
def count_dollars_upward(sum_needed: int) -> int:
"""Return the number of ways to make change using bills.
>>> count_dollars_upward(15) # 15 $1 bills, 10 $1 & 1 $5 bills, ... 1 $5 & 1 $10 bills
6
>>> count_dollars_upward(10) # 10 $1 bills, 5 $1 & 1 $5 bills, 2 $5 bills, 10 $1 bills
4
>>> count_dollars_upward(20) # 20 $1 bills, 15 $1 & $5 bills, ... 1 $20 bill
10
>>> count_dollars_upward(45) # How many ways to make change for 45 dollars?
44
>>> count_dollars_upward(100) # How many ways to make change for 100 dollars?
344
>>> count_dollars_upward(200) # How many ways to make change for 200 dollars?
3274
>>> from construct_check import check
>>> # ban iteration
>>> check(SOURCE_FILE, 'count_dollars_upward', ['While', 'For'])
True
"""
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q count_dollars_upward
考试练习
下面是一些往年考试中的相关题目,供你练习。这些题目为选做,也无需提交。