红魔咖啡馆

头发越掉越多,头发越掉越少

0%

WWPD部分省略

Print If

返回列表中满足函数f的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def print_if(s, f):
    """Print each element of s for which f returns a true value.

    >>> print_if([3, 4, 5, 6], lambda x: x > 4)
    5
    6
    >>> result = print_if([3, 4, 5, 6], lambda x: x % 2 == 0)
    4
    6
    >>> print(result)  # print_if should return None
    None
    """
    for x in s:
        if f(x):
            print(x)
    return None

Close

返回列表中元素大小与下标差的绝对值小于规定值的元素个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def close(s, k):
    """Return how many elements of s that are within k of their index.

    >>> t = [6, 2, 4, 3, 5]
    >>> close(t, 0)  # Only 3 is equal to its index
    1
    >>> close(t, 1)  # 2, 3, and 5 are within 1 of their index
    3
    >>> close(t, 2)  # 2, 3, 4, and 5 are all within 2 of their index
    4
    >>> close(list(range(10)), 0)
    10
    """
    count = 0
    for i in range(len(s)):  # Use a range to loop over indices
        if abs(s[i]-i)<=k :
            count+=1
    return count

Close List

返回一个列表,元素为给定列表中元素大小与下标差的绝对值小于规定值的元素

1
2
3
4
5
6
7
8
9
10
11
12
def close_list(s, k):
    """Return a list of the elements of s that are within k of their index.

    >>> t = [6, 2, 4, 3, 5]
    >>> close_list(t, 0)  # Only 3 is equal to its index
    [3]
    >>> close_list(t, 1)  # 2, 3, and 5 are within 1 of their index
    [2, 3, 5]
    >>> close_list(t, 2)  # 2, 3, 4, and 5 are all within 2 of their index
    [2, 4, 3, 5]
    """
    return [s[i] for i in range(len(s)) if s[i]-i<=k]

Squares Only

返回一个列表,元素为给定列表中为完全平方数的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from math import sqrt

def squares(s):
    """Returns a new list containing square roots of the elements of the
    original list that are perfect squares.

    >>> seq = [8, 49, 8, 9, 2, 1, 100, 102]
    >>> squares(seq)
    [7, 3, 1, 10]
    >>> seq = [500, 30]
    >>> squares(seq)
    []
    """
    return [int(sqrt(n)) for n in s if sqrt(n)==round(sqrt(n))]

注意返回的是开方后的数,是整数

Double Eights

使用递归判断给定的数中是否存在相邻的两个8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def double_eights(n):
    """ Returns whether or not n has two digits in row that
    are the number 8. Assume n has at least two digits in it.

    >>> double_eights(1288)
    True
    >>> double_eights(880)
    True
    >>> double_eights(538835)
    True
    >>> double_eights(284682)
    False
    >>> double_eights(588138)
    True
    >>> double_eights(78)
    False
    >>> from construct_check import check
    >>> # ban iteration
    >>> check(LAB_SOURCE_FILE, 'double_eights', ['While', 'For'])
    True
    """
    if n==0:
        return False
    if n%100==88:
        return True
    else:
        return double_eights(n//10)

Making Onions

判断是否能通过f函数与g函数进行至多limit次操作实现将x转化为y

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def make_onion(f, g):
    """Return a function can_reach(x, y, limit) that returns
    whether some call expression containing only f, g, and x with
    up to limit calls will give the result y.

    >>> up = lambda x: x + 1
    >>> double = lambda y: y * 2
    >>> can_reach = make_onion(up, double)
    >>> can_reach(5, 25, 4)      # 25 = up(double(double(up(5))))
    True
    >>> can_reach(5, 25, 3)      # Not possible
    False
    >>> can_reach(1, 1, 0)      # 1 = 1
    True
    >>> add_ing = lambda x: x + "ing"
    >>> add_end = lambda y: y + "end"
    >>> can_reach_string = make_onion(add_ing, add_end)
    >>> can_reach_string("cry", "crying", 1)      # "crying" = add_ing("cry")
    True
    >>> can_reach_string("un", "unending", 3)     # "unending" = add_ing(add_end("un"))
    True
    >>> can_reach_string("peach", "folding", 4)   # Not possible
    False
    """
    def can_reach(x, y, limit):
        if limit < 0:
            return False
        elif x == y:
            return True
        else:
            return can_reach(f(x), y, limit - 1) or can_reach(g(x), y, limit - 1)
    return can_reach

Project2-Cat

实现一个金山打字通(?)

具体实现:记录打字速度以及自动修正拼写错误的字符

Phase 1: Typing

实现打字以及检测打字速度相关功能

Problem 1

挑选用户打字的段落

pick函数

参数:

  • paragraphs:一串字符串记录了打字内容
  • select:一个函数,检测段落是否能被选择
  • k:非负数,作为index

思路:

  • 函数功能实现了用户选择第k个段落作为打字内容
  • 若选择的k没有对应段落则返回空字符串
  • 选择的字符串要符合select函数的条件
  • 符合条件的才能编号第k个字符串

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def pick(paragraphs, select, k):
    """Return the Kth paragraph from PARAGRAPHS for which SELECT called on the
    paragraph returns True. If there are fewer than K such paragraphs, return
    the empty string.

    Arguments:
        paragraphs: a list of strings
        select: a function that returns True for paragraphs that can be selected
        k: an integer

    >>> ps = ['hi', 'how are you', 'fine']
    >>> s = lambda p: len(p) <= 4
    >>> pick(ps, s, 0)
    'hi'
    >>> pick(ps, s, 1)
    'fine'
    >>> pick(ps, s, 2)
    ''
    """
    # BEGIN PROBLEM 1
    valid_para = []
    for s in paragraphs:
        if select(s):
            valid_para.append(s)
    if k>=len(valid_para):
        return ''
    else:
        return valid_para[k]
    # END PROBLEM 1

Problem 2

通过给定的关键词选取段落

about函数

参数:subject:关键词列表

思路:

  • about函数用于pick函数中的select选项,用于筛选指定关键字的段落
  • 故它返回的是一个select函数,若段落满足条件则返回True否则返回False
  • 单词匹配时不区分大小写,但要是一个完整单词,且不能是单词的字串
  • 可以使用给定的函数remove_punctuation-去除标点,lower-变小写,split-将一句话分割为若干单词存入列表

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def about(subject):
    """Return a select function that returns whether
    a paragraph contains one of the words in SUBJECT.

    Arguments:
        subject: a list of words related to a subject

    >>> about_dogs = about(['dog', 'dogs', 'pup', 'puppy'])
    >>> pick(['Cute Dog!', 'That is a cat.', 'Nice pup!'], about_dogs, 0)
    'Cute Dog!'
    >>> pick(['Cute Dog!', 'That is a cat.', 'Nice pup.'], about_dogs, 1)
    'Nice pup.'
    """
    assert all([lower(x) == x for x in subject]), 'subjects should be lowercase.'
    # BEGIN PROBLEM 2
    def select(para):
        sp_para = split(remove_punctuation(para))
        for i in range(0,len(sp_para)):
            sp_para[i]=lower(sp_para[i])
        for s in subject:
            if s in sp_para:
                return True
        return False
    return select

    # END PROBLEM 2

Problem 3

计算已经输入且匹配单词占需要输入内容的百分比

accuracy函数

参数:

  • typed:已经输入的内容
  • source:需要输入的内容

思路:

  • 按单词顺序匹配指定输入内容,第一个对第一个,第二个对第二个….
  • 区分大小写,且包含标点符号
  • 若已经输入内容比需要输入内容长,则长的部分认定为不正确
  • 若两者均为空字符串则准确率为100.0,若前者为空后者非空或前者非空后者为空则准确率为0.0

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def accuracy(typed, source):
    """Return the accuracy (percentage of words typed correctly) of TYPED
    when compared to the prefix of SOURCE that was typed.

    Arguments:
        typed: a string that may contain typos
        source: a string without errors

    >>> accuracy('Cute Dog!', 'Cute Dog.')
    50.0
    >>> accuracy('A Cute Dog!', 'Cute Dog.')
    0.0
    >>> accuracy('cute Dog.', 'Cute Dog.')
    50.0
    >>> accuracy('Cute Dog. I say!', 'Cute Dog.')
    50.0
    >>> accuracy('Cute', 'Cute Dog.')
    100.0
    >>> accuracy('', 'Cute Dog.')
    0.0
    >>> accuracy('', '')
    100.0
    """
    typed_words = split(typed)
    source_words = split(source)
    # BEGIN PROBLEM 3
    cnt = 0
    for t, s in zip(typed_words, source_words):
        if t==s:
            cnt+=1
    if len(typed_words)==0 and len(source_words)==0:
        return 100.0
    elif len(typed_words)==0 or len(source_words)==0:
        return 0.0
    else:
        return (cnt/len(typed_words))*100
    # END PROBLEM 3

注意:

  • 同时遍历两个列表时,使用zip函数解包,因为原代码实现的是遍历一个包含两个列表的元组
  • 空字符部分特判,因为计算百分比时可能会导致分母为0
  • 计算的是已经输入字符与需要输入字符匹配的字符个数在已经输入字符中的占比,分母是typed_words的长度

Problem 4

按照words/min计算打字速度

wpm函数

参数:

  • typed:已经输入的内容
  • elapsed:总共打字时间(按秒计)

思路:

  • wpm的计算是按照字符数来的,单位是每五个字符数,这样可以减少单词长度对结果的影响
  • \(wpm = \frac{\frac{字符数}{5}}{时间(min)}\)

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def wpm(typed, elapsed):
    """Return the words-per-minute (WPM) of the TYPED string.

    Arguments:
        typed: an entered string
        elapsed: an amount of time in seconds

    >>> wpm('hello friend hello buddy hello', 15)
    24.0
    >>> wpm('0123456789',60)
    2.0
    """
    assert elapsed > 0, 'Elapsed time must be positive'
    # BEGIN PROBLEM 4
    length = len(typed)
    return (length/5)/(elapsed/60)
    # END PROBLEM 4

注意:elapsed单位是秒,wpm使用的时间是分钟

Phase 2: Autocorrect

按下空格触发单词自动纠正,若最近的一个词接近正确词汇但不正确,则会用正确词汇替代

Problem 5

返回一个列表,内部提供了几个接近输入单词的正确单词

参数:

  • typed_word:一个字符串,表示输入的单词
  • word_list:源单词列表
  • diff_function:评估单词不同程度的函数
  • limit:单词能否被更改的阈值

思路:

  • autocorrect实现的

    • 若输入字符已经在word_list中,将会直接返回该字符
    • 否则会返回基于diff函数计算的不同程度最小的单词
    • 若输入单词与word_list中最小的不同程度仍大于limit,则返回输入单词
  • 所有输入单词和word_list中的单词都是小写且没有标点

  • 若多个单词与输入字符不同程度最小相同,则返回最靠前的

  • 不同程度可以使用两个单词相同部分长度衡量

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def autocorrect(typed_word, word_list, diff_function, limit):
    """Returns the element of WORD_LIST that has the smallest difference
    from TYPED_WORD. If multiple words are tied for the smallest difference,
    return the one that appears closest to the front of WORD_LIST. If the
    difference is greater than LIMIT, instead return TYPED_WORD.

    Arguments:
        typed_word: a string representing a word that may contain typos
        word_list: a list of strings representing source words
        diff_function: a function quantifying the difference between two words
        limit: a number

    >>> ten_diff = lambda w1, w2, limit: 10 # Always returns 10
    >>> autocorrect("hwllo", ["butter", "hello", "potato"], ten_diff, 20)
    'butter'
    >>> first_diff = lambda w1, w2, limit: (1 if w1[0] != w2[0] else 0) # Checks for matching first char
    >>> autocorrect("tosting", ["testing", "asking", "fasting"], first_diff, 10)
    'testing'
    """
    # BEGIN PROBLEM 5
    if typed_word in word_list:
        return typed_word
    word_diff = [diff_function(typed_word, s, limit) for s in word_list]
    min_diff = min(word_diff)
    if min_diff > limit:
        return typed_word
    else :
        return word_list[word_diff.index(min_diff)]

    # END PROBLEM 5

注意:

  • 可以使用list.index(<val>)获取列表中某值的下标

Problem 6

返回为纠正单词需要修改的字符个数

参数:

  • typed:输入单词
  • source:目标单词
  • limit:最多修改字符数

思路:

  • 比较个位字符,若不同则执行更改,记录更改数

  • 若一边比另一边长,长度的不同也算入更改数

  • 若更改数比limit打,则返回任何大于limit的数(为了避免多余的计算)

  • 要求使用递归

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def feline_fixes(typed, source, limit):
    """A diff function for autocorrect that determines how many letters
    in TYPED need to be substituted to create SOURCE, then adds the difference in
    their lengths and returns the result.

    Arguments:
        typed: a starting word
        source: a string representing a desired goal word
        limit: a number representing an upper bound on the number of chars that must change

    >>> big_limit = 10
    >>> feline_fixes("nice", "rice", big_limit)    # Substitute: n -> r
    1
    >>> feline_fixes("range", "rungs", big_limit)  # Substitute: a -> u, e -> s
    2
    >>> feline_fixes("pill", "pillage", big_limit) # Don't substitute anything, length difference of 3.
    3
    >>> feline_fixes("roses", "arose", big_limit)  # Substitute: r -> a, o -> r, s -> o, e -> s, s -> e
    5
    >>> feline_fixes("rose", "hello", big_limit)   # Substitute: r->h, o->e, s->l, e->l, length difference of 1.
    5
    """
    # BEGIN PROBLEM 6
    # assert False, 'Remove this line'
    def compute(typed_w,source_w,count):
        if count > limit:
            return limit+1
        if typed_w == "" and source_w == "":
            return count
        elif typed_w == "":
            return compute(typed_w,source_w[1:],count+1)
        elif source_w == "":
            return compute(typed_w[1:],source_w,count+1)
        elif typed_w[0]==source_w[0]:
            return compute(typed_w[1:],source_w[1:],count)
        else:
            return compute(typed_w[1:],source_w[1:],count+1)
    return compute(typed,source,0)
    # END PROBLEM 6

注意:

  • 超过limit限制的一律设为limit+1
  • 通过slice来每次递归往后截取一位字符,比较每次截取后的首字符来判断相同与否,不相同让count+1
  • 递归出口为两字符串均被截取成空串,返回计数
  • 长度不同的情况下,已经被截成空串的不再截取,长串继续截取,该情况下直接让count+1

Problem 7

返回将输入字符改为目标字符所需要执行的操作次数,操作有以下:

  • 添加字符
  • 删除字符
  • 替换字符

参数:

  • typed:输入单词
  • source:目标单词
  • limit:最多修改字符数

思路:

  • 比较输入单词与指定单词,寻找不同位置
  • 若需要修改数量大于limit,则返回任何大于limit的值
  • 代码需要使用递归,应需要三个递归调用以及两个递归出口

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def minimum_mewtations(typed, source, limit):
    """A diff function that computes the edit distance from TYPED to SOURCE.
    This function takes in a string TYPED, a string SOURCE, and a number LIMIT.
    Arguments:
        typed: a starting word
        source: a string representing a desired goal word
        limit: a number representing an upper bound on the number of edits
    >>> big_limit = 10
    >>> minimum_mewtations("cats", "scat", big_limit)       # cats -> scats -> scat
    2
    >>> minimum_mewtations("purng", "purring", big_limit)   # purng -> purrng -> purring
    2
    >>> minimum_mewtations("ckiteus", "kittens", big_limit) # ckiteus -> kiteus -> kitteus -> kittens
    3
    """
    # assert False, 'Remove this line'
    if limit<0: # Base cases should go here, you may add more base cases as needed.
        # BEGIN
        return 0
        # END
    # Recursive cases should go below here
    if typed == "" and source == "": # Feel free to remove or add additional cases
        # BEGIN
        return 0
        # END
    elif typed == "" or source == "":
        return abs(len(typed)-len(source))
    elif typed[0] == source[0]:
        return minimum_mewtations(typed[1:],source[1:],limit)
    else:
        add = minimum_mewtations(typed,source[1:],limit-1)
        remove = minimum_mewtations(typed[1:],source,limit-1)
        substitute = minimum_mewtations(typed[1:],source[1:],limit-1)
        # BEGIN
        return min(add,remove,substitute)+1
        # END

注意:

  • 和上一个problem类似,使用递归与slice一位位判断

  • 三种操作依次递归,并取最小值

Phase 3: Multiplayer

实现多人对战模式

Problem 8

将玩家输入进度与信息传入多人服务器并返回对应信息

report_progress函数

参数:

  • typed:输入的字符,列表存储
  • source:需要输入的字符,列表存储
  • user_id:当前玩家id
  • upload:上传进度的函数

思路:

  • 将输入字符与需要输入字符比较,计算输入进度
  • 输入进度指已经输入正确单词与需要输入正确单词的比例(因此,若中间有一个单词打错,后面再正确也不会记录)
  • 函数的返回值是计算的进度

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def report_progress(typed, source, user_id, upload):
    """Upload a report of your id and progress so far to the multiplayer server.
    Returns the progress so far.

    Arguments:
        typed: a list of the words typed so far
        source: a list of the words in the typing source
        user_id: a number representing the id of the current user
        upload: a function used to upload progress to the multiplayer server

    >>> print_progress = lambda d: print('ID:', d['id'], 'Progress:', d['progress'])
    >>> # The above function displays progress in the format ID: __, Progress: __
    >>> print_progress({'id': 1, 'progress': 0.6})
    ID: 1 Progress: 0.6
    >>> typed = ['how', 'are', 'you']
    >>> source = ['how', 'are', 'you', 'doing', 'today']
    >>> report_progress(typed, source, 2, print_progress)
    ID: 2 Progress: 0.6
    0.6
    >>> report_progress(['how', 'aree'], source, 3, print_progress)
    ID: 3 Progress: 0.2
    0.2
    """
    # BEGIN PROBLEM 8
    typed_count = 0
    for t,s in zip(typed,source):
        if t != s:
            break
        typed_count+=1
    ratio = typed_count/len(source)
    upload({'id': user_id, 'progress': ratio})
    return ratio
    # END PROBLEM 8

注意:

  • 判断是否相等时,若发现不等就需要break结束循环

Problem 9

计算两个玩家输入每个单词时的用时差

time_per_word函数

参数:

  • words:按输入顺序排列的单词列表
  • timestamps_per_player:一个二维列表,记录了每位玩家开始与结束输入每个单词的时间

data abstraction:match

参数:

  • words:输入单词组成的单词列表
  • times:二维列表,记录了每个玩家输入每个单词需要多长时间,如times[i][j]指玩家i输入单词words[j]所花费的时间

思路:

  • timestamps_per_player记录的是每个玩家敲每个单词的开始与结束时间
  • 传入match后,时间会自动计算为敲每个单词使用的时间(结束-开始),并存入每个单词key对应的value中
  • 通过get_all_word与get_all_times函数可以返回match中对应的键与值形成的列表
  • 通过get_word与time函数可以返回指定的键与值

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def time_per_word(words, timestamps_per_player):
    """Given timing data, return a match data abstraction, which contains a
    list of words and the amount of time each player took to type each word.

    Arguments:
        words: a list of words, in the order they are typed.
        timestamps_per_player: A list of lists of timestamps including the time
                          the player started typing, followed by the time
                          the player finished typing each word.

    >>> p = [[75, 81, 84, 90, 92], [19, 29, 35, 36, 38]]
    >>> match = time_per_word(['collar', 'plush', 'blush', 'repute'], p)
    >>> get_all_words(match)
    ['collar', 'plush', 'blush', 'repute']
    >>> get_all_times(match)
    [[6, 3, 6, 2], [10, 6, 1, 2]]
    """
    # BEGIN PROBLEM 9
    times = []
    for l in timestamps_per_player:
        each_times = []
        for i in range(1,len(l)):
            each_times.append(l[i]-l[i-1])
        times.append(each_times)
    return match(words,times)

    # END PROBLEM 9

注意:

  • 该函数的作用是通过调用match函数构造该抽象类型

  • 但match函数需要每个单词的时间差,故需要在该函数中处理输入的时间列表(结束-开始)

  • 将处理后的列表与传入该函数的单词列表传入match函数并返回获得的抽象类型

Problem 10

返回每个单词哪位玩家敲得块

fastest_words函数

参数:

  • match:match函数中获得的抽象数据类型

思路:

  • 比较多个玩家输入每个字符时间,选择输入时间最少的
  • 返回的列表中存储了每个玩家输入最快的单词组成的列表
  • 若多个玩家输入时间相同,则取编号更小的玩家

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def fastest_words(match):
    """Return a list of lists of which words each player typed fastest.

    Arguments:
        match: a match data abstraction as returned by time_per_word.

    >>> p0 = [5, 1, 3]
    >>> p1 = [4, 1, 6]
    >>> fastest_words(match(['Just', 'have', 'fun'], [p0, p1]))
    [['have', 'fun'], ['Just']]
    >>> p0  # input lists should not be mutated
    [5, 1, 3]
    >>> p1
    [4, 1, 6]
    """
    player_indices = range(len(get_all_times(match)))  # contains an *index* for each player
    word_indices = range(len(get_all_words(match)))    # contains an *index* for each word
    # BEGIN PROBLEM 10
    result = [[] for i in player_indices]
    for i in word_indices:
        min_player = -1
        for j in player_indices:
            if min_player == -1 or time(match, j, i) < time(match, min_player, i):
                min_player = j
        result[min_player].append(get_word(match,i))
    return result
    # END PROBLEM 10

注意:

  • 列表中创建多个列表要用for表达式[[] for _ in player_indices]
  • result列表中下标表示玩家,对应元素表示该玩家最快的单词组成的列表
  • 我们首先遍历单词,然后遍历玩家找出时间最小的玩家,并将对应单词存入该玩家对应列表

Project1-Hog

实现一个掷骰子游戏

规则

两位玩家依次掷任意数量(不多于10个)骰子,点数之和先达到GOAL的胜利。

  • Sow Sad: 若其中一个骰子点数为一,则该玩家此轮得分为1
  • Boar Brawl: 玩家可以选择不掷骰子,得分为max(1,3*abs(对手得分十位数-自己得分个位数)),位数不够补零。
  • Sus Fuss: 若一局结束后,玩家点数数值有三或四个因数(包括1和点数本身),该玩家点数会变为比当前数值大的最近的一个质数

Phase 1: Rules of the Game

模拟游戏的进行

Problem 0

熟悉dice.py,了解骰子的生成

  • make_fair_dice()用于生成每面概率相等的SIDE面骰子
  • make_test_dice()用于测试循环投出一系列指定值

掷出一次骰子的方法:调用生成骰子赋值给的变量e.g. six_sided()

Problem 1

实现掷骰子函数,并实现规则Sow Sad

在定义函数时,若出现形参后已经赋值的情况,该值代表该函数不传入参数时形参默认值

def roll_dice(num_rolls, dice=six_sided),若不传入dice参数则默认为six_sided

roll_dice()函数:

参数:

  • num_rolls指掷骰子次数
  • dice指传入的骰子(默认值为六面骰子)

理解:

  • 该函数返回的是num_roll次结果之和,若其中有1则返回1
  • 若上次调用函数掷骰子次数少于掷骰子次数,则下次掷骰子时会接着上次开始而非从头开始
  • 在循环中return语句会结束一个循环

My code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def roll_dice(num_rolls, dice=six_sided):
    """Simulate rolling the DICE exactly NUM_ROLLS > 0 times. Return the sum of
    the outcomes unless any of the outcomes is 1. In that case, return 1.

    num_rolls:  The number of dice rolls that will be made.
    dice:       A function that simulates a single dice roll outcome. Defaults to the six sided dice.
    """
    # These assert statements ensure that num_rolls is a positive integer.
    assert type(num_rolls) == int, 'num_rolls must be an integer.'
    assert num_rolls > 0, 'Must roll at least once.'
    # BEGIN PROBLEM 1
    total = 0
    flag = 0
    for i in range(num_rolls):
        temp = dice()
        if temp==1:
            flag = 1
        total += temp
    if flag:
        return 1
    else:
        return total
    # END PROBLEM 1

Problem 2

实现规则Boar Brawl

boar_brawl()函数:

参数:

  • player_score:自己的分数
  • opponent_score:对手的分数

理解:

  • 获取自己分数的个位数与对手分数的十位数
  • 相减并取绝对值后乘三
  • 若小于1则输出1,否则输出计算结果

My code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def boar_brawl(player_score, opponent_score):
    """Return the points scored by rolling 0 dice according to Boar Brawl.

    player_score:     The total score of the current player.
    opponent_score:   The total score of the other player.

    """
    # BEGIN PROBLEM 2
    player_one = player_score%10
    opponent_ten = opponent_score%100//10
    result = 3*abs(opponent_ten-player_one)
    if result>=1:
        return result
    else:
        return 1

    # END PROBLEM 2

注意:传入的参数不一定是十位数,需要进行一些处理

Problem 3

实现函数,将前两个规则结合,输出正确结果

take_turn()函数:

参数:

  • num_rolls:掷骰子次数
  • player_score:自己的分数
  • opponent_score:对手的分数
  • dice:使用的骰子

理解:

  • 若num_rolls>0,则按照正常规则进行,获得本轮掷骰点数之和
  • 若num_rolls=0,即不掷骰子,等同于选择使用Boar Brawl规则,执行该函数

My code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def take_turn(num_rolls, player_score, opponent_score, dice=six_sided):
    """Return the points scored on a turn rolling NUM_ROLLS dice when the
    player has PLAYER_SCORE points and the opponent has OPPONENT_SCORE points.

    num_rolls:       The number of dice rolls that will be made.
    player_score:    The total score of the current player.
    opponent_score:  The total score of the other player.
    dice:            A function that simulates a single dice roll outcome.
    """
    # Leave these assert statements here; they help check for errors.
    assert type(num_rolls) == int, 'num_rolls must be an integer.'
    assert num_rolls >= 0, 'Cannot roll a negative number of dice in take_turn.'
    assert num_rolls <= 10, 'Cannot roll more than 10 dice.'
    # BEGIN PROBLEM 3
    if num_rolls==0:
        return boar_brawl(player_score,opponent_score)
    else:
        return roll_dice(num_rolls,dice)
    # END PROBLEM 3

Problem 4

实现Sus Fuss规则

num_factors()函数

参数:

  • n:要计算的数n

理解:

  • 返回数n的因数个数
  • 1和n本身也算进去

My code:

1
2
3
4
5
6
7
8
9
def num_factors(n):
    """Return the number of factors of N, including 1 and N itself."""
    # BEGIN PROBLEM 4
    total = 0
    for i in range(1,n+1):
        if n%i==0:
            total+=1
    return total
    # END PROBLEM 4

sus_points()函数

参数:

  • score:某玩家的分数

理解:

  • 该函数用于更新玩家在Sus Fuss规则下新的分数
  • 即用于判断符合条件下大于该分数数值的下一个质数
  • 若该数值不符合条件,则返回本身

My code:

1
2
3
4
5
6
7
8
9
10
11
def sus_points(score):
    """Return the new score of a player taking into account the Sus Fuss rule."""
    # BEGIN PROBLEM 4
    if num_factors(score)==3 or num_factors(score)==4:
        while True:
            score+=1
            if is_prime(score):
                return score
    else:
        return score
    # END PROBLEM 4

sus_update()函数

参数:

  • num_rolls:掷骰子次数
  • player_score:自己的分数
  • opponent_score:对手的分数
  • dice:使用的骰子

理解:

  • 用于输出num_rolls次掷骰子后,考虑以上三种规则后的点数之和
  • 现根据take_turn()函数求出满足前两个规则的本轮分数之和,并累加到当前分数上
  • 再判断此时分数是否满足第三个规则,进行相应分数改动

My code:

1
2
3
4
5
6
7
8
def sus_update(num_rolls, player_score, opponent_score, dice=six_sided):
    """Return the total score of a player who starts their turn with
    PLAYER_SCORE and then rolls NUM_ROLLS DICE, *including* Sus Fuss.
    """
    # BEGIN PROBLEM 4
    score = player_score + take_turn(num_rolls, player_score, opponent_score, dice)
    return sus_points(score)
    # END PROBLEM 4

Problem 5

完整实现游戏模拟

play()函数

参数:

  • strategy0:player0使用的策略
  • strategy1:player1使用的策略
  • update:使用的更新函数(有无sus)
  • score0:player0的起始分数
  • score1:player1的起始分数
  • dice:使用的骰子
  • goal:实现游戏结束的数值

理解:

  • strategy指的是玩家掷骰子数量
  • strategy函数传入自己与对手的分数,根据两者分数得出下次掷骰子数量
  • 使用传入的update函数来决定分数改变策略(是否采用sus fuss规则)

My code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def play(strategy0, strategy1, update,
         score0=0, score1=0, dice=six_sided, goal=GOAL):
    """Simulate a game and return the final scores of both players, with
    Player 0's score first and Player 1's score second.

    E.g., play(always_roll_5, always_roll_5, sus_update) simulates a game in
    which both players always choose to roll 5 dice on every turn and the Sus
    Fuss rule is in effect.

    A strategy function, such as always_roll_5, takes the current player's
    score and their opponent's score and returns the number of dice the current
    player chooses to roll.

    An update function, such as sus_update or simple_update, takes the number
    of dice to roll, the current player's score, the opponent's score, and the
    dice function used to simulate rolling dice. It returns the updated score
    of the current player after they take their turn.

    strategy0: The strategy for player0.
    strategy1: The strategy for player1.
    update:    The update function (used for both players).
    score0:    Starting score for Player 0
    score1:    Starting score for Player 1
    dice:      A function of zero arguments that simulates a dice roll.
    goal:      The game ends and someone wins when this score is reached.
    """
    who = 0  # Who is about to take a turn, 0 (first) or 1 (second)
    # BEGIN PROBLEM 5
    while score0<goal and score1 < goal:
        if who==0:
            score0 = update(strategy0(score0, score1), score0, score1, dice)
        else:
            score1 = update(strategy1(score1, score0), score1, score0, dice)
        who = 1 - who
    # END PROBLEM 5
    return score0, score1

Phase 2: Strategies

这部分将会根据自己与对手的分数生成每轮玩家的掷骰数(0-10)

Problem 6

返回一个函数,获取自己与对手分数并输出指定骰子个数

理解:

  • 返回的是函数,有两个参数:自己与对手分数
  • 无论两者分数多少,返回的总是一开始传入的指定骰子个数

My code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def always_roll(n):
    """Return a player strategy that always rolls N dice.

    A player strategy is a function that takes two total scores as arguments
    (the current player's score, and the opponent's score), and returns a
    number of dice that the current player will roll this turn.

    >>> strategy = always_roll(3)
    >>> strategy(0, 0)
    3
    >>> strategy(99, 99)
    3
    """
    assert n >= 0 and n <= 10
    # BEGIN PROBLEM 6
    return lambda x,y: n
    # END PROBLEM 6

Problem 7

判断每种分数组合是否都有一种对应的掷骰个数(分数组合指一种自己与对手的分数)

is_always_roll()函数:

参数:

  • strategy:掷骰策略
  • goal:玩家胜利要达到的目标分数

理解:

  • 自己与对手的得分在胜利之前可能性均有100种(0-99),故可能性组合有10000种
  • 该函数实现了判断每种可能性组合下返回的掷骰数是否相同
  • goal不一定是100

My code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def is_always_roll(strategy, goal=GOAL):
    """Return whether STRATEGY always chooses the same number of dice to roll
    given a game that goes to GOAL points.

    >>> is_always_roll(always_roll_5)
    True
    >>> is_always_roll(always_roll(3))
    True
    >>> is_always_roll(catch_up)
    False
    """
    # BEGIN PROBLEM 7
    num = strategy(0,0)
    for i in range(0,goal):
        for j in range(0,goal):
            if strategy(i,j)!=num:
                return False
    return True
    # END PROBLEM 7

Problem 8

返回一个函数,用来调用n次掷骰函数,并返回这n次掷骰得到的值之和的平均值

语法特性:*args

*args参数允许函数接受任意数量的位置参数,以元组形式传入

args可以改为其他名称,*必须有

应用:

  1. 定义的函数接受不定数量位置参数时
  2. 编写高阶函数(higher-order function)时,传递参数给内部的定义的函数

make_averaged()函数

参数:

  • original_function:调用的掷骰函数
  • times_called:调用次数

理解:

  • 函数需要做到执行n次掷骰函数,并将返回值累加,最后求这n次和的平均值
  • 在内联函数中使用了*args来表示若干参数,与调入的掷骰函数参数数量一致

My code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def make_averaged(original_function, times_called=1000):
    """Return a function that returns the average value of ORIGINAL_FUNCTION
    called TIMES_CALLED times.

    To implement this function, you will have to use *args syntax.

    >>> dice = make_test_dice(4, 2, 5, 1)
    >>> averaged_dice = make_averaged(roll_dice, 40)
    >>> averaged_dice(1, dice)  # The avg of 10 4's, 10 2's, 10 5's, and 10 1's
    3.0
    """
    # BEGIN PROBLEM 8
    def average_cal(*args):
        suma = 0
        for i in range(times_called):
            suma+=original_function(*args)
        return suma/times_called
    return average_cal

    # END PROBLEM 8

Problem 9

实现函数,枚举掷骰次数(1-10),看哪次的分数平均值最大

max_scoring_num_rolls()函数

参数:

  • dice:使用的骰子
  • times_called:调用次数

理解

  • 遍历掷骰次数,调用make_averaged函数计算平均值,取1-10掷骰次数中平均值最大值
  • 当平均值相等时取更小的

My code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def max_scoring_num_rolls(dice=six_sided, times_called=1000):
    """Return the number of dice (1 to 10) that gives the maximum average score for a turn.
    Assume that the dice always return positive outcomes.

    >>> dice = make_test_dice(1, 6)
    >>> max_scoring_num_rolls(dice)
    1
    """
    # BEGIN PROBLEM 9
    maxi = 0
    maxc = 0
    for i in range(1,11):
        temp = make_averaged(roll_dice,times_called)(i,dice)
        if temp > maxc:
            maxc = temp
            maxi = i
    return maxi
    # END PROBLEM 9

Problem 10

采用Boar Brawl规则,若roll出0时根据该规则得到的分数比threshold大,返回0,否则返回掷骰次数

boar_strategy()函数

参数:

  • score:自己的分数
  • opponent_score:对手的分数
  • threshold:阈值分数,若采用boar brawl得到的分数大于它则返回掷骰0次
  • num_rolls:掷骰次数

理解:

  • boar brawl规则是在掷骰0次下的特殊规则
  • 若采用该规则策略获得的分数要比正常掷骰获得的分数更高,就采用该策略(返回掷骰次数0次)
  • 否则按正常掷骰次数掷骰

My code:

1
2
3
4
5
6
7
8
def boar_strategy(score, opponent_score, threshold=11, num_rolls=6):
    """This strategy returns 0 dice if Boar Brawl gives at least THRESHOLD
    points, and returns NUM_ROLLS otherwise. Ignore score and Sus Fuss.
    """
    # BEGIN PROBLEM 10
    if boar_brawl(score,opponent_score)>=threshold: return 0
    return num_rolls  # Remove this line once implemented.
    # END PROBLEM 10

Problem 11

采用Sus Fuss规则,若roll出0时根据该规则与Boar Brawl规则得到的分数与起始分数差值比threshold大,返回0,否则返回掷骰次数

sus_strategy()函数

参数:

  • score:自己的分数
  • opponent_score:对手的分数
  • threshold:阈值分数,若采用boar brawl得到的分数大于它则返回掷骰0次
  • num_rolls:掷骰次数

理解:

  • 即在boar brawl规则下采用sus fuss规则得到分数
  • 若采用该规则策略获得的分数要比正常掷骰获得的分数更高,就采用该策略(返回掷骰次数0次)
  • 否则按正常掷骰次数掷骰

My code:

1
2
3
4
5
6
def sus_strategy(score, opponent_score, threshold=11, num_rolls=6):
    """This strategy returns 0 dice when your score would increase by at least threshold."""
    # BEGIN PROBLEM 11
    if sus_update(0,score,opponent_score) - score >=threshold: return 0
    return num_rolls  # Remove this line once implemented.
    # END PROBLEM 11

Problem 12

结合上述所有策略与自己的策略,实现最终策略

My code:

1
2
3
4
5
6
7
8
9
10
def final_strategy(score, opponent_score):
    """Write a brief description of your final strategy.

    *** YOUR DESCRIPTION HERE ***
    """
    # BEGIN PROBLEM 12
    threshold = GOAL-score
    if score>80 and score-opponent_score>20: return 0
    return 6  # Remove this line once implemented.
    # END PROBLEM 12

Containers

Slicing

切片可以简洁的获取列表中的子列表,实质是创建了新的列表而不影响原始列表

如列表odds = [3,5,7,9,11]

  • odds[1:3] -> [5,7] 默认也是包括起始值不包括结束值
  • odds[:3] -> [3,5,7] 省略开头将从列表头开始算起
  • odds[1:] -> [5,7,9,11] 省略结尾将覆盖至列表尾

内置函数

sum

sum(<可迭代对象>, [起始值])

返回所有可迭代元素和并加上起始值,起始值默认为0

sum还可以实现列表相加:sum([[2,3],[4]],[]) 注意起始值类型要和前面相同

max

max(<可迭代对象>, [键函数])

max(a,b,c,..., [键函数])

返回对象中的最大值或返回若干值中的最大值

键函数:考虑对每个元素执行键函数并根据返回值比较大小(返回原始值)

e.g. max(range(10), key=lambda x: 7-(x-4)*(x-2))

all

all(<可迭代对象>)

对每个元素执行bool(x)操作,若所有元素都返回True则返回True,否则返回False,空列表返回True

e.g. all([x<5 for x in range(5)]) 返回True

len

len(<可迭代对象>)

获取某个序列长度

有关string

string可以用于表示:数据、语言、程序

表示字符串

  • 使用单引号
  • 使用双引号
  • 使用一对连续的三个双引号:可以跨行

string也是一种sequence

求解长度与选择元素与列表相同

获取到的string中的元素本身也是一个字符串,但只有它本身一个元素

in与not in可以在字符串中寻找连续字符

e.g. 'here' in 'where's waldo? 返回True

Dictionary

dictionary用于存储键值对,使用花括号和冒号分隔键值

创建:

numerals = {'I':1, 'V':5, 'X':10}

可以用数字,字符串当键值,列表或字典 当值

注意:键本身不能是列表或字典,键不能重复

查找:

输入对应的键来查找对应的值,但不能通过值查找键

numerals['V'] >>> 5

遍历:

dictionary是键的序列,通过list()创建列表可以获得所有的键,因此可以用for循环遍历所有的键

使用numerals.value()获得字典中所有的值,存储在一个序列中(非列表)

Dictionary Comprehensions

可以使用表达式来创建列表

格式:{<key exp>: <val exp> for <name> in <iter exp> if <filter exp>}

过程:

  1. 添加以当前作用域为父作用域的新作用域
  2. 建立一个空的result字典存储表达式的值
  3. 对每个中的元素:
    1. 在新作用域中将绑定到每一个元素
    2. 为真,则将配对并添加到result字典中

e.g.

{x*x: x for x in [1,2,3,4,5] if x>2} 结果为{9:3, 16:4, 25:3}

Sequence

Lists

列表是python中的内置数据类型

  • 使用[]创建列表
  • 使用赋值语句命名列表
  • 使用name[下标]访问列表元素(0-index),或使用getitem( ,)(operator模块)来访问元素
  • 使用len()函数获取元素个数

name[下标]是一种元素评估表达式,可以依次评估下标对应元素

列表中的元素可以是表达式,此时列表中的值将绑定为表达式的结果

digits = [2//2,2+2,2,2*2]

列表间可以相加与相乘(运算符或函数)

[2,7]+[1,8,2,8]*2 == [2,7,1,8,2,8,1,8,2,8]

列表中的元素可以是任何东西,包括列表

1
2
pairs = [[10,20],[30,40]]
pairs[1][0] # 结果是30

in运算符

in运算符可以判断某元素是否存在于列表中

not in 运算符可以判断某元素是否不存在于列表中

返回值为True 或 False

这两个运算符寻找的是单独元素,而非子序列

e.g.

1
2
3
4
5
6
7
8
9
digits = [1,8,2,8]
 >>> 1 in digits
True
>>> '1' in digits
False
>>> [1,2] in [[1,2],3,4]
True
>>> [1,2] in [[[1,2],3],4]
False

For Statement

执行过程

1
2
for <name> in <expression>:
    <suite>
  • 执行,该表达式必须是一个可迭代的量(如一个序列)
  • 对序列中的每个元素,按序进行如下操作:
    • 在当前作用域中将每个元素赋给
    • 执行语句块中的语句

### 序列解包

e.g.寻找序列中相同元素的数对

pairs = [[1,2],[2,2],[3,2],[4,4]]

1
2
3
for x, y in paris:
    if x==y:
        same_count+=1

该循环中,for循环会自动将paris中的内层列表中的两个元素赋给x,y两个变量

Ranges

range是表示连续整数的序列,通过给出起始与结束值获得范围内的整数

range包括起始值而不包括结束值

用法:range(<起始值(默认为0)>, <结束值>, [步长])

作用:

  • 计算长度:结束值-起始值
  • 选择元素:起始值+index
  • 作为计数器:用_等当作name而不实际使用它

转换列表:使用list() - 列表构造函数

List Comprehension

列表中可以写一些语句来构造列表

e.g.1

1
2
3
4
5
>>> odds = [1,3,5,7,9]
>>> [x+1 for x in odds]
[2,4,6,8,10]
>>> [x for x in odds if 25%x == 0]
[1,5]

e.g.2 查找某个数的因数

1
2
def divisors(n):
    return [1]+[x for x in range(2,n) if n%x==0]

Homework 3

Q1: Num Eights

用递归函数求一个数中有几位8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def num_eights(n):
    """Returns the number of times 8 appears as a digit of n.

    >>> 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(HW_SOURCE_FILE, 'num_eights',
    ...       ['Assign', 'AnnAssign', 'AugAssign', 'NamedExpr', 'For', 'While'])
    True
    """
    if n==0:
        return 0
    else:
        if n%10 ==8: return num_eights(n//10)+1
        else: return num_eights(n//10)

递归出口:每一位都减完后n==0时

每次返回时递归调用自身,传入未判断的部分

若发现该位是8,则返回值加一,否则不变

Q2: Digit Distance

用递归函数求每两位的差的绝对值之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def digit_distance(n):
    """Determines the digit distance of n.

    >>> digit_distance(3)
    0
    >>> digit_distance(777)
    0
    >>> digit_distance(314)
    5
    >>> digit_distance(31415926535)
    32
    >>> digit_distance(3464660003)
    16
    >>> from construct_check import check
    >>> # ban all loops
    >>> check(HW_SOURCE_FILE, 'digit_distance',
    ...       ['For', 'While'])
    True
    """
    if n<10:
        return 0
    else:
        res,last = n//10,n%10
        sec_last = res%10
        return digit_distance(res)+abs(sec_last-last)

递归出口:当查到最后一位时绝对值为0,返回0

否则将最后一位与倒数第二位取出,将取完最后一位的剩余部分传入递归函数继续判断,返回值加上两位数绝对值之差

Q3: Interleaved Sum

写一个函数,要求对1-n中的所有奇数传入odd_func,所有偶数传入even_func,返回所有数计算后和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def interleaved_sum(n, odd_func, even_func):
    """Compute the sum odd_func(1) + even_func(2) + odd_func(3) + ..., up
    to n.

    >>> 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(HW_SOURCE_FILE, 'interleaved_sum', ['While', 'For', 'Mod']) # ban loops and %
    True
    """
    def check_num(k):
        if k>n:
            return 0
        elif k==n:
            return odd_func(k)
        else:
            return check_num(k+2)+odd_func(k)+even_func(k+1)
    return check_num(1)

由于题目不让使用循环与取模运算判断奇偶,我们只能使用递归函数

由于奇数与偶数是分开的,我们可以发现,奇数+2=奇数,奇数+1=偶数

因此我们可以写一个内嵌函数,让一个计数变量k从1开始,将k传入odd_func,k+1传入even_func

然后递归调用该函数从k+2开始

递归出口即k>n或k=n(此时k一定为奇数,直接传入odd_func并返回)

Q4: Count Coins

给予n刀乐,把他分为面值分别为1刀乐,5刀乐,10刀乐,25刀乐的四种货币,输出分法数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def count_coins(total):
    """Return the number of ways to make change using coins of value of 1, 5, 10, 25.
    >>> count_coins(15)
    6
    >>> count_coins(10)
    4
    >>> count_coins(20)
    9
    >>> count_coins(100) # How many ways to make change for a dollar?
    242
    >>> count_coins(200)
    1463
    >>> from construct_check import check
    >>> # ban iteration
    >>> check(HW_SOURCE_FILE, 'count_coins', ['While', 'For'])
    True
    """
    def cal(total,spl_coin):
        if total==0:
            return 1
        elif total<0:
            return 0
        elif spl_coin==None:
            return 0
        else:
            with_coin = cal(total-spl_coin,spl_coin)
            without_coin = cal(total,next_smaller_coin(spl_coin))
            return with_coin+without_coin
    return cal(total,25)

Q5: Towers of Hanoi

实现汉诺塔游戏并描述每次移动过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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"
    def hanoi(n,start,mid,end):
        if n == 1:
            print_move(start, end)
            return
        else:
            hanoi(n - 1, start,end, mid)
            hanoi(1,start,mid,end)
            hanoi(n - 1, mid, start,end)
    return hanoi(n,start,6-start-end,end)

详见五点七边讲解视频

Tree Recursion

Order of Recursive Calls

e.g.1:Cascade

1
2
3
4
5
6
7
def cascade(n):
    if n<10:
        print(n)
    else:
        print(n)
        cascade(n//10)
        print(n)

结果为

1
2
3
4
5
6
7
8
9
10
>>> cascade(12345)
12345
1234
123
12
1
12
123
1234
12345

首先一直调用cascade到底,返回None,从调用入口出来后继续执行cascade下面的语句

cascade

还可以缩短为以下

1
2
3
4
5
def cascade_short(n):
    print(n)
    if n>10:
        cascade(n//10)
        print(n)

e.g.2:Inverse Cascade

1
2
3
4
5
6
7
8
9
10
11
12
def inverse_cascade(n):
    grow(n)
    print(n)
    shrink(n)
    
def f_then_g(f,g,n):
    if n:
        f(n)
        g(n)

grow = lambda n: f_then_g(grow,print,n//10)
shrink = lambda n: f_then_g(print,shrink,n//10)

grow先进行处理,每次将数字缩短一节,到达递归底部后退出时便是从小到大依次输出

shrink先打印出来当前n,然后将数字缩短一节,这样递归过程便实现了从大到小依次输出

Tree Recursion

当递归函数对自身调用超过一次时,发生树形递归,产生树状过程

e.g.1 斐波那契数列

树形斐波那契

1
2
3
4
5
6
7
def fib(n):
    if n==0:
        return 0
    elif n==1:
        return 1
    else:
        return fib(n-1)+fib(n-2)

e.g.2 计算分区

将正整数n分为大小不超过m的分区的方式有多少种,即n能以多少种方式表示为递增的不超过m部分之和

e.g. count_partitions(6,4)

有以下可能:

\(2+4=6\)

\(1+1+4=6\)

\(3+3=6\)

\(1+2+3=6\)

\(1+1+1+3=6\)

……

分为两种情况考虑:

  • 至少分一个4
  • 不分4

这样我们可以把递归问题拆分为两个小问题,将两种情况相加

  • count_partitions(2,4)

  • count_partitions(6,3)

    以此类推,count_partitions(6,3)按同样方式考虑,分3与不分3,直到递归底部

1
2
3
4
5
6
7
8
9
10
11
def count_partitions(n,m):
    if n==0:
        return 1
    elif n<0:
        return 0
    elif m==0:
        return 0
    else:
        with_m = count_partitions(n-m,m)
        without_m = count_partitions(n,m-1)
        return with_m+without_m