家庭作业 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:数字距离

对于一个整数, 数字距离 是相邻数字之差的绝对值之和。例如:

  • 下面这个数的数字距离 615,因为要计算的绝对值为 6 - 15的单行调用。
  • 下面这个数的数字距离 7125312 (abs(7-1) + abs(1-2) + abs(2-5) + abs(5-3) = 6 + 1 + 3 + 2).
  • 下面这个数的数字距离 60 ,因为它没有相邻数字对。

编写函数,计算正整数的数字距离。必须使用递归,否则测试会失败。

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_dollarnext_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),返回一个新列表,把原序列前半部分与后半部分的元素 交错排列 。该函数不会修改 ss的单行调用。

所谓 交错排列 两个序列 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 的求值结果是 True if a 是列表。

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

考试练习

下面是一些往年考试中的相关题目,供你练习。这些题目为选做,也无需提交。

  1. 2017 秋季期中考试 1 问题 4a: 数字
  2. 2019 秋季期末考试问题 6b: 回文