红魔咖啡馆

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

0%

Mutablity

Objects

  • 对象用于表达信息,是一种包含了数据与行为的数据抽象
  • 有属性的东西都可以作为对象,python中一切皆对象
  • Python中,优先级最高的对象被称为类(class)
  • 面向对象编程(OOP):
    • 对象是OOP的核心
    • OOP使用一种暗喻来组织大型程序
    • 使用一种特殊语法可以提高程序的可读性与组织
  • 很多数据操作都是通过对象实现的
  • 对象可以做许多相关的事情,而函数只能做一件事

e.g. String

字符串是一种表达文本的数据抽象 ### 字符串的表示 目前常见的表示方法是用ASCII与Unicode字符集表示字符 前者包含了控制字符、数字、字母与标点,后者则包含了不同语言中的更多字符

1
2
3
4
from unicodedata import name, lookup
print(name('A')) # 查询字符集中的字符名称
print(lookup('BABY')) # 根据字符名称输出对应字符
print(lookup('BABY').encode()) # 查看该字符的字节编码
LATIN CAPITAL LETTER A
👶
b'\xf0\x9f\x91\xb6'

Mutation Operations

一些对象是可以改变的

1
2
3
4
5
6
7
8
9
10
11
suits = ['coin', 'string', 'myriad']
original_suits = suits
print(suits.pop()) # 弹出一个元素(默认最后一个)
suits.remove('string') # 移除指定元素
print(suits)
suits.append('cup') # 在尾部增加一个元素
suits.extend(['sword', 'club']) # 添加序列中的多个元素来拓展列表
print(suits)
suits[2] = 'spade'
suits[0:2] = ['heart', 'diamond']
print(original_suits)
myriad
['coin']
['coin', 'cup', 'sword', 'club']
['heart', 'diamond', 'spade', 'club']

根据以上代码发现,我们可以对一个对象(suits)进行若干操作来变化其值 而我们在最初将suits与original_suits进行绑定,故变化也会在original_suits中体现 综上,所有指向相同对象的names都会受到Mutation的影响,其中这里的mutation指对象发生的变化 只有可变类型的对象才能更改,如列表与字典

函数调用时发生的Mutation

函数可以更改其作用域中任何可变对象的值

1
2
3
4
5
def mystery(s):
    s.pop()
    s.pop()
four = [1,2,3,4]
mystery(four)

mystery函数实现了对列表对象值的更改,甚至mystery不需要传入参数,直接更改其所在作用域(全局作用域)中four列表的内容

表达式的Mutation

表达式的值会随着names绑定值或对象的改变而改变

1
2
3
4
x = [1,2]
print(x+x)
x.append(3)
print(x+x)
[1, 2, 1, 2]
[1, 2, 3, 1, 2, 3]

Tuples

  • 元组是一种不可变的序列,使用圆括号包裹起来
  • 实际上,任何以逗号隔开的元素都会被解释成元组,非必须加圆括号
  • 使用tuple()创建元组或将其他序列转化为元组
  • 在单个元素后加一个逗号可以将单个元素转化成元组
  • 元组可以相加,也可以使用成员运算符in来判断元素是否存在
  • 由于元组不可变,可以将其作为字典的键使用
  • 若元组中包含了可变对象,则该对象可以被更改
1
2
3
s = ([1,2],3)
s[0][0]=4
print(s)
([4, 2], 3)

Mutation

相同与改变

1
2
3
4
5
a = [10]
b = a
print(a==b)
a.append(20)
print(a==b)
True
True

在这个例子中,我们可以说a与b是相同的,因为b与a绑定到了同一个对象,当对其中一个发生变化,另一个也会同时改变

1
2
3
4
5
a = [10]
b = [10]
print(a==b)
b.append(20)
print(a==b)

在这个例子中,a与b是不同的,尽管它们曾有过相同的内容,但对b进行改变后,a会随之改变,因此这时二者便不同了

Identity Operators

Identity<exp0> is <exp1> 当两个表达式指向相同对象时返回True Equality <exp0> == <exp1> 当两个表达式拥有相同值时返回True

相同对象始终拥有相等的值,但反之不一定成立

1
2
3
4
5
a = [10]
b = [10]
c = b
print(a is b)
print(b is c)
False
True

可变对象在函数中的默认值

函数中声明的默认值是函数值的一部分,而不是每次调用时才生成 这导致了若该对象是可变的,而且在函数中间进行了修改,则该修改会在下次调用函数时保留 如下面的代码,每次调用增加的值会保留在默认值中

1
2
3
4
5
def f(s=[]):
    s.append(3)
    return len(s)
for i in range(3):
    print(f())
1
2
3

Mutable Fuctions

在函数中使用可变对象可以在多次调用时保留上次操作的值

1
2
3
4
5
6
7
8
9
10
11
12
13
def make_withdraw_list(balance):
    b = [balance]
    def withdraw(amount):
        if amount > b[0]:
            return 'Insufficient funds'
        b[0] = b[0] - amount
        return b[0]
    return withdraw

withdraw = make_withdraw_list(100)
print(withdraw(25))
print(withdraw(25))
withdraw(100)
75
50
'Insufficient funds'

该函数将存款存在作为可变对象的列表中 函数中的withdraw函数始终指向列表b并修改其值,该列表总是这个列表,随着时间其中的内容被更改 为了实现每次更改列表中的值,该函数使用了可变对象来创建了一个可变函数 function

Trees

描述树形结构的术语

递归描述 - 一课树有一个根节点和一系列分支节点 - 每个分支也是一棵树,也有根节点与分支节点 - 没有分支节点的树被称为叶子节点

亲戚描述

  • 树的每个位置被称为节点
  • 每个节点可以表示任何值
  • 一个节点可以称为另一个节点的父节点/子节点 trees

实现树形结构的抽象

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
def tree(label, branches=[]):
    for branch in branches:
        assert is_tree(branch), "branches must be trees" # 确保构造的是一棵树
    return [label] +list(branches) # 将同一层的分支节点放在一个列表中

def label(tree):
    return tree[0]

def branches(tree):
    return tree[1:]

def is_tree(tree):
    """判断是不是一棵树"""
    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): 
    """判断树本身是不是叶子节点"""
    return not branches(tree)


if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(t)
    print(label(t))
    print(branches(t))
    print(label(branches(t)[0]))
[1, [5, [7]], [6]]
1
[[5, [7]], [6]]
5

Tree Processing

处理叶子节点

使用递归,将会在每个分支节点进行递归调用,最后合并

1
2
3
4
5
6
7
8
9
10
def count_leaves(t):
    """Count the leaves of tree T"""
    if is_leaf(t):
        return 1
    else:
        return sum([count_leaves(b) for b in branches(t)])

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(count_leaves(t))
2

返回叶子节点的值

实现leaves函数,返回树的叶子节点的值的列表

1
2
3
4
5
6
7
8
9
10
def leaves(tree):
    """return a list containing the leaf labels of tree"""
    if is_leaf(tree):
        return [label(tree)]
    else:
        return sum([leaves(b) for b in branches(tree)], [])

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(leaves(t))
[7, 6]

根据树创建树

使用递归根据另一棵树创建一颗新树 如让叶子节点值+1的树 或让所有节点都+1的树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def increment_leaves(t):
    """return a tree like t but with leaf labels incremented"""
    if is_leaf(t):
        return tree(label(t)+1)
    else:
        bs = [increment_leaves(b) for b in branches(t)]
        return tree(label(t), bs)

def increment(t):
    """return a tree like t but with all labels incremented"""
    return tree(label(t)+1, [increment(b) for b in branches(t)])

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(leaves(t))
    print(increment_leaves(t))
    print(increment(t))
[7, 6]
[1, [5, [8]], [7]]
[2, [6, [8]], [7]]

例:print_tree

按节点在树中的深度缩进打印一颗树

1
2
3
4
5
6
7
8
def print_tree(t, indent = 0):
    print(' '*indent + str(label(t)))
    for b in branches(t):
        print_tree(b, indent+1)

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(print_tree(t))
1
 5
  7
 6
None

例: 求从根节点沿路径到叶子节点求和并打印

1
2
3
4
5
6
7
8
9
10
11
def print_sums(t, so_far):
    so_far = so_far +label(t)
    if is_leaf(t):
        print(so_far)
    else:
        for b in branches(t):
            print_sums(b,so_far)

if __name__ == "__main__":
    t = tree(1, [tree(5, [tree(7)]), tree(6)])
    print(print_sums(t, 0))
13
7
None

Dictionaries 部分跳过

Divide

传入一组商数与一组除数,返回字典,键为每个商数,值为表示每个商数能整除的除数的列表

1
2
3
4
5
6
7
8
9
10
def divide(quotients, divisors):
    """Return a dictonary in which each quotient q is a key for the list of
    divisors that it divides evenly.

    >>> divide([3, 4, 5], [8, 9, 10, 11, 12])
    {3: [9, 12], 4: [8, 12], 5: [10]}
    >>> divide(range(1, 5), range(20, 25))
    {1: [20, 21, 22, 23, 24], 2: [20, 22, 24], 3: [21, 24], 4: [20, 24]}
    """
    return {i: [x for x in divisors if x%i==0] for i in quotients}

注意用表达式建立列表与字典的方法

Buying Fruit

实现buy函数,通过给定的水果与价格,用恰好为传入的total_amount的价格购买指定的水果(每种指定水果至少买一次) 用display函数输出所有结果

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 buy(required_fruits, prices, total_amount):
    """Print ways to buy some of each fruit so that the sum of prices is amount.

    >>> prices = {'oranges': 4, 'apples': 3, 'bananas': 2, 'kiwis': 9}
    >>> buy(['apples', 'oranges', 'bananas'], prices, 12)
    [2 apples][1 orange][1 banana]
    >>> buy(['apples', 'oranges', 'bananas'], prices, 16)
    [2 apples][1 orange][3 bananas]
    [2 apples][2 oranges][1 banana]
    >>> buy(['apples', 'kiwis'], prices, 36)
    [3 apples][3 kiwis]
    [6 apples][2 kiwis]
    [9 apples][1 kiwi]
    """
    def add(fruits, amount, cart):
        if fruits == [] and amount == 0:
            print(cart)
        elif fruits and amount > 0:
            fruit = fruits[0]
            price = prices[fruit]
            for k in range(1,amount//price+1):
                add(fruits[1:], amount-k*price, cart+display(fruit,k))
    add(required_fruits, total_amount, '')

def display(fruit, count):
    """Display a count of a fruit in square brackets.

    >>> display('apples', 3)
    '[3 apples]'
    >>> display('apples', 1)
    '[1 apple]'
    """
    assert count >= 1 and fruit[-1] == 's'
    if count == 1:
        fruit = fruit[:-1]  # get rid of the plural s
    return '[' + str(count) + ' ' + fruit + ']'

返回条件是fruits为空或钱被花光,且递归时每次都取fruits的首个元素 可以推断递归时对fruits数组进行了切片,每次往后切一个元素 for循环遍历选择每个水果的个数,从至少选一个到最多能选的个数\(\frac{amount}{price}+1\) 递归时传入总价格减去已经使用的价格数的不同情况,并使用display函数进行字符串拼接来显示

Cities ADT

以下问题基于建立的该ADT 一个城市由以下参数描述:名称、经度、维度 包括一个构造函数 - make_city(name, lat, lon):建立一个城市对象,存储其名称、经度、维度 以下选择器 - get_name(city):获取城市名称 - get_lat(city):获取城市经度 - get_lon(city):获取城市维度 该抽象数据类型已经在文件中实现,你不需要知道是怎么实现的

Distance

计算并返回两城市的距离

1
2
3
4
5
6
7
8
9
10
11
12
13
from math import sqrt
def distance(city_a, city_b):
    """
    >>> city_a = make_city('city_a', 0, 1)
    >>> city_b = make_city('city_b', 0, 2)
    >>> distance(city_a, city_b)
    1.0
    >>> city_c = make_city('city_c', 6.5, 12)
    >>> city_d = make_city('city_d', 2.5, 15)
    >>> distance(city_c, city_d)
    5.0
    """
    return sqrt(abs(get_lat(city_a)-get_lat(city_b))**2+abs(get_lon(city_a)-get_lon(city_b))**2)

Closer City

比较两个城市离给定经纬度的远近,返回更近的那个城市

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def closer_city(lat, lon, city_a, city_b):
    """
    Returns the name of either city_a or city_b, whichever is closest to
    coordinate (lat, lon). If the two cities are the same distance away
    from the coordinate, consider city_b to be the closer city.

    >>> berkeley = make_city('Berkeley', 37.87, 112.26)
    >>> stanford = make_city('Stanford', 34.05, 118.25)
    >>> closer_city(38.33, 121.44, berkeley, stanford)
    'Stanford'
    >>> bucharest = make_city('Bucharest', 44.43, 26.10)
    >>> vienna = make_city('Vienna', 48.20, 16.37)
    >>> closer_city(41.29, 174.78, bucharest, vienna)
    'Bucharest'
    """
    fake_city = make_city('fake', lat, lon)
    if distance(fake_city,city_a)<distance(fake_city,city_b):
        return get_name(city_a)
    else:
        return get_name(city_b)

可以将指定的经纬度看作第三个城市并构造这么一个对象 通过上面已经写完的distance函数计算两城市与第三个城市的距离并比较,输出更近的 若相同输出city_b

Data Abstraction

Data Abstraction

大多数值都是复合值,由多种对象组成

抽象数据类型可以让我们将符合对象作为一个单元操作,允许我们隔离使用数据的程序的两个部分:

  • 数据的表示
  • 数据的操作

即在数据的表示与操作之间建立一个抽象屏障

Example——Rational Numbers

表示

任何有理数可以表示为一个最简分数

这为小数提供了更精确的表示方法

故我们需要分开分子分母,作为一个复合数据类型:

  • rational(n,d)返回一个有理数x
  • numer(x)返回有理数x的分子
  • denom(x)返回有理数的分母

第一个函数称为构造函数(constructor),它用于构造一个新值作为抽象数据类型的实例(instance)

第二、三个函数称为选择器(selectors),它们返回得到的有理数的整数部分

这三个函数作为有理数的抽象数据类型使用,用它们进行数字操作

算术

根据小学二年级知识,我们根据分数来执行有理数加乘,公式如下

有理数算术
1
2
3
4
5
6
7
8
9
10
def mul_rational(x,y):
    return rational(numer(x)*numer(y),denom(x)*denom(y))

def add_rational(x,y):
    nx, dx = numer(x), denom(y)
    ny, dy = numer(y), denom(y)
    return rational(nx*dy+ny*dx, dx*dy)

def equal_rational(x,y):
    return numer(x)*denom(y)==numer(y)*denom(x)

Abstraction Barriers

拿上方的有理数函数作为例子

抽象屏障

抽象屏障表示使用由有理数计算时使用的函数只能是对应相关的,而不能越界表示

  • 用有理数计算时只需要使用计算相关函数
  • 用来表示有理数和进行操作时只需要使用对应构造函数与选择器
  • 用来构建构造函数与选择器时需要用到列表与元素选择

一种跨越了抽象屏障的反例:

1
2
3
4
add_rational([1,2],[1,4])

def divide_rational(x,y):
    return [x[0]*y[1], x[1]*y[0]]

Pair

创建与访问

通常使用列表表示一个pair

1
2
3
>>> pair = [1,2]
>>> pair
[1,2]

通过序列解包或列表下标获得pair中的两个值

1
2
3
4
5
6
7
>>> x,y=pair
>>> x
1
>>> y
2
>>> pair[0]
1

还可以用getitem函数获得值,在operator库中

用法:getitem(<list>, <index>)

1
2
3
4
>>> getitem(pair, 0)
1
>>> getitem(pair, 1)
2

用pair表示有理数

1
2
3
def rational(n,d):
    """Construct a rational number that represents N/D"""
    return [n, d]

返回一个列表,记录分子分母用来表示有理数

1
2
3
4
5
6
7
def numer(x):
    """Return the numerator of rational number x"""
    return x[0]

def denom(x):
    """Return the denominator of rational number x"""
    return x[1]

通过访问列表中的元素返回分子分母

应用

分数通分

有理数的分子分母应互质,故我们可以用gcd获取它们的最大公因数来同时除以分子分母以确保获得互质的分子分母

1
2
3
4
from fractions import gcd
def rational(n,d):
    g = gcd(n,d)
    return [n//g, d//g]

Data Representation

数据抽象的基本思想:通过它的行为来识别该抽象数据类型

改变有理数的表现形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def rational(n,d):
    def select(name):
        if name == 'n':
            return n
        elif name == 'd':
            return d
    return select
def numer(x):
    """Return the numerator of rational number x"""
    return x('n')

def denom(x):
    """Return the denominator of rational number x"""
    return x('d')

这里使x成为一个函数,这样就可以不需要内置的列表数据类型了

此时rational是一个高阶函数,返回一个表示有理数的函数

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