红魔咖啡馆

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

0%

Representations

String Representations

字符串用来表达语言与程序
Python中,有两种字符串表达 - str:对人类可读 - repr:对解释器刻可读 这两种一般相同,但也有不同之处
### repr String for an Object repr函数返回一个python表达式(字符串),该表达式会评估为一个等同的对象
对一个对象的repr调用eval会给你一个与原始对象等价的对象
调用repr的结果与在python交互界面输出的结果相同

str String for an Object

str函数接受任何对象,返回一个字符串,字符串是原始对象的人类可以解释的表示
对表达式调用str的结果与调用print时输出的结果相同

F-strings

String Interpolation

字符串插值包括对一些含有表达式的字符串字面量求值 - 可以使用+进行字符串连接实现 'pi starts with'+str(pi)+'...' - 也可以使用f-string功能实现字符串插值 f'pi starts with {pi}' 花括号中的将会作为表达式看待并给出计算结果 f-string的计算结果包括str string和子表达式的值

Polymorphic Functions

多态函数是一种适用于多种数据类型的函数
如str和repr函数都是多态函数,它们接受任何类型的对象
原理:repr调用了一个零参数方法__repr__以便在参数上获取他返回的repr字符串
str同样调用一个 零参数方法__str__
对于repr函数:

  • repr函数内部只调用了一个名为repr的类属性,而实例属性__repr__被忽略了
  • 实现如下:
1
2
def repr(x):
    return type(x).__repr__(x)

对于str函数: - 实例属性__str__被忽略了 - 若类上根本没有__str__属性,则调用str只会返回repr返回的内容

Interface

对象传递信息的方式:通过互相查找属性或方法
属性的查找规则允许不同数据类型通过具有相同属性名称来响应相同信息

共享信息:在许多不同类上引起相似行为的属性名称
接口:一组共享信息与一些规范(表示其含义)

如实现__repr__与__str__的类实现了一个接口,一下是用类来展示该接口的代码

1
2
3
4
5
6
7
8
9
class Ratio:
    def __init__(self, n, d):
        self.numer = n
        self.denom = d

    def __repr__(self):
        return 'Ratio({0}, {1})'.format(self.numer, self.denom)
    def __str__(self):
        return '{0}/{1}'.format(self.numer, self.denom)

当我们令half = Ratio(1,2),直接打印half,输出为可读版本,而直接在解释器展示half,就会输出python表达式

Special Method Names

python中,以两个下划线包围的名字表示它们具有某种内置行为 - __init__:构造对象时会自动调用的方法 - __repr__:将对象以python表达式的方式输出,在交互式python界面时用于显示值的方法 - __add__:用于将一个对象加入另一个对象 - __bool__:用于将一个对象转换为布尔值 - __float__:用于将一个对象转换为实数 同时,以下代码实现了将两个对象相加

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
class Ratio:
    def __init__(self, n, d):
        self.numer = n
        self.denom = d

    def __repr__(self):
        return 'Ratio({0}, {1})'.format(self.numer, self.denom)
    def __str__(self):
        return '{0}/{1}'.format(self.numer, self.denom)
    def __add__(self, other):
        if isinstance(other, int):
            n = self.numer+self.denom*other
            d = self.denom
        elif isinstance(other, Ratio):
            n = self.numer*other.denom +self.denom*other.numer
            d = self.denom*other.denom
        elif isinstance(other, float):
            return float(self)+other
        g = gcd(n,d)
        return Ratio(n//g, d//g)
    __radd__ = __add__

    def __float__(self):
        return self.numer/self.denom

def gcd(n, d):
    while n!=d:
        n, d = min(n, d), abs(n-d)
    return n

Inheritance

继承是将多个类关联起来的一种方法 当两个类相似时,可以使用继承,相似的类可能具有与通用类相同的所有属性,外加自带的特殊属性 语法:

1
2
class <name>(<base class>):
    <suite>
通过该语句创建的子类与其基类共享所有属性,子类可能会覆盖某些继承的属性,但未更改的保持不变 当编写子类是,只需要指出其与基类不同之处即可 ## 例 创建一个支票账户(CheckingAccount)类,作为账户(Account)类的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
# 类的继承
class CheckingAccount(Account):
    """A bank account that charges for withdrawals"""
    withdraw_fee = 1
    interest = 0.01
    def withdraw(self, amount):
        return Account.withdraw(self, amount + self.withdraw_fee)

ch = CheckingAccount('Tom')
print(ch.interest)
ch.deposit(20)
ch.withdraw(5)
print(ch.balance)

此处我们在Account类的基础上修改了interest属性与withdraw方法,而其余属性与方法可以继续使用

在类上寻找属性名称

基类属性不会复制到子类中,确保了未经修改的属性与基类保持一致,在类中寻找名称遵循如下规则 - 如果名称为类中的属性,则返回属性值 - 若不在,在其基类中寻找该名称

面向对象设计

在进行面向对象编程时,建议遵循如下原则 - 不要重复复制粘贴已经存在的,而是利用已经存在的实现 - 已被覆盖的属性仍需要通过类来访问

如以上代码,在计算提款时最佳方式是查找实例本身上的withdraw_fee 这样,如果当前实例有一个特定的withdraw_fee,我们就使用它,否则使用Account类中的

继承与组合

继承最适合is-a关系,如支票账户是(is a)账户的特定类型,故适合使用继承 组合最适合has-a关系,如一个银行具有(has a)有一组他管理的账户,该组账户是它的一个属性,而不会继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Bank:
    """a bank has accounts"""
    def __init__(self):
     self.accounts=[]   # 账户列表

    def open_account(self, holder, amount, kind = Account):
        account = kind(holder)   # 创建某种类型的账户
        account.deposit(amount)   # 存入amount的钱
        self.accounts.append(account)   # 将创建的账户加入当前银行的账户列表
        return account

    def pay_interest(self):
        # 计算列表中的利息
        for a in self.accounts:
            a.deposit(a.balance*a.interest)

bank = Bank()
john = bank.open_account('john', 10)
jack = bank.open_account('jack', 5, CheckingAccount)
bank.pay_interest()
print(jack.balance)
print(john.balance)

多重继承

一个子类可以有多个基类 其中,这里直接调用父类account_holder时,使用super()实现了超类调用,防止破坏多重继承的调用逻辑

1
2
3
4
5
6
7
8
9
10
# 类的多重继承
class SavingsAccount(Account):
    deposit_fee = 2
    def deposit(self, amount):
        return Account.deposit(self, amount-self.deposit_fee)
class GoodAccount(CheckingAccount, SavingsAccount):
    def __init__(self, account_holder):
        super().__init__(account_holder)
        self.holder = account_holder
        self.balance = 1

Decomposition

Modular Design

程序的设计原则:将程序的不同部分分离开来,使得每个模块可以独立开发与测试 以以下餐厅搜索代码为例,实现从文件中读取评分与评论(使用similarity评估相似性)搜索对应评分前k个餐厅去过的其他餐厅的功能:

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
40
def search(query, ranking = lambda r: -r.stars):
    results  =[r for r in Restaurant.all if query in r.name]
    return sorted(results, key= ranking)
def reviewed_both(r, s):
    return len([x for x in r.reviewers if x in s.reviewers])
class Restaurant:
    all = []
    def __init__(self, name, star, reviewer):
        self.name = name
        self.star = star
        self.reviewer = reviewer
        Restaurant.all.append(self)

    def similar(self, k, similarity=reviewed_both):
        """Return the k most similar restaurants to self"""
        others = Restaurant.all
        others.remove(self)
        different = lambda r: -similarity(r, self)
        return sorted(others, key=different)[:k]


    def __repr__(self):
        return '<'+self.name+'>'

import json
reviewers_for_restaurant = {}
for line in open('reviews.json'):
    r = json.loads(line)
    biz = r['business_id']
    if biz not in reviewers_for_restaurant:
        reviewers_for_restaurant[biz] = [r['user_id']]
    else:
        reviewers_for_restaurant[biz].append(r['user_id'])
for line in open('restaurant.json'):
    r = json.loads(line)
    reviewers = reviewers_for_restaurant[r['business_id']]
    Restaurant(r['name'], r['stars'], reviewers)
results = search('Thai')
for r in results:
    print(r,'share reviewer with', r.similar(3))

优化时间复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
def reviewed_both(r, s):
    return fast_overlap(r.reviewers, s.reviewers)

def fast_overlap(s, t):
    count, i, j = 0, 0, 0
    while i<len(s) and j<len(t):
        if s[i]==t[j]:
            count, i, j = count+1, i+1, j+1;
        elif s[i]<t[i]:
            i+=1
        else:
            j+=1
    return count

Homework 5

Q1: Infinite Hailstone

写一个生成器函数来生成从n开始的冰雹猜想序列,当到达序列尾后,生成器会一直声称1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def hailstone(n):
    """Q1: Yields the elements of the hailstone sequence starting at n.
       At the end of the sequence, yield 1 infinitely.

    >>> hail_gen = hailstone(10)
    >>> [next(hail_gen) for _ in range(10)]
    [10, 5, 16, 8, 4, 2, 1, 1, 1, 1]
    >>> next(hail_gen)
    1
    """
    yield n
    if n==1:
        n=1
    elif n%2==0:
        n=int(n/2)
    else:
        n=3*n+1
    yield from hailstone(n)

使用递归,结合yield from生成迭代器

Q2: Merge

写一个merge函数,传入两个无限生成器a,b按照给定起始位置与步长生成非重复上升序列,返回一个生成器包含两个生成器中的所有元素,且去重

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 merge(a, b):
    """Q2:
    >>> def sequence(start, step):
    ...     while True:
    ...         yield start
    ...         start += step
    >>> a = sequence(2, 3) # 2, 5, 8, 11, 14, ...
    >>> b = sequence(3, 2) # 3, 5, 7, 9, 11, 13, 15, ...
    >>> result = merge(a, b) # 2, 3, 5, 7, 8, 9, 11, 13, 14, 15
    >>> [next(result) for _ in range(10)]
    [2, 3, 5, 7, 8, 9, 11, 13, 14, 15]
    """
    gene_a = next(a)
    gene_b = next(b)
    while True:
        if gene_a == gene_b:
            yield gene_a
            gene_a = next(a)
            gene_b = next(b)
        elif gene_a> gene_b:
            yield gene_b
            gene_b = next(b)
        else:
            yield gene_a
            gene_a = next(a)

Q3: Yield Paths

定义一个生成器函数,传入一棵树与一个值v,返回生成器从树的根到含有v的节点的每一条路径

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 yield_paths(t, value):
    """Q4: Yields all possible paths from the root of t to a node with the label
    value as a list.

    >>> t1 = tree(1, [tree(2, [tree(3), tree(4, [tree(6)]), tree(5)]), tree(5)])
    >>> print_tree(t1)
    1
      2
        3
        4
          6
        5
      5
    >>> next(yield_paths(t1, 6))
    [1, 2, 4, 6]
    >>> path_to_5 = yield_paths(t1, 5)
    >>> sorted(list(path_to_5))
    [[1, 2, 5], [1, 5]]

    >>> t2 = tree(0, [tree(2, [t1])])
    >>> print_tree(t2)
    0
      2
        1
          2
            3
            4
              6
            5
          5
    >>> path_to_2 = yield_paths(t2, 2)
    >>> sorted(list(path_to_2))
    [[0, 2], [0, 2, 1, 2]]
    """
    if label(t) == value:
        yield [value]
    for b in branches(t):
        for p in yield_paths(b, value):
            yield [label(t)]+p

If our current label is equal to value, we’ve found a path from the root to a node containing value containing only our current label, so we should yield that. From there, we’ll see if there are any paths starting from one of our branches that ends at a node containing value. If we find these “partial paths” we can simply add our current label to the beginning of a path to obtain a path starting from the root.

In order to do this, we’ll create a generator for each of the branches which yields these “partial paths”. By calling yield_paths on each of the branches, we’ll create exactly this generator! Then, since a generator is also an iterable, we can iterate over the paths in this generator and yield the result of concatenating it with our current label.

WWPD部分省略

Q2: Insert Items

实现一个函数,传入列表,一个值before,一个值after,在列表中每个等于before的之后插入after

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 insert_items(s, before, after):
    """Insert after into s after each occurrence of before and then return s.

    >>> test_s = [1, 5, 8, 5, 2, 3]
    >>> new_s = insert_items(test_s, 5, 7)
    >>> new_s
    [1, 5, 7, 8, 5, 7, 2, 3]
    >>> test_s
    [1, 5, 7, 8, 5, 7, 2, 3]
    >>> new_s is test_s
    True
    >>> double_s = [1, 2, 1, 2, 3, 3]
    >>> double_s = insert_items(double_s, 3, 4)
    >>> double_s
    [1, 2, 1, 2, 3, 4, 3, 4]
    >>> large_s = [1, 4, 8]
    >>> large_s2 = insert_items(large_s, 4, 4)
    >>> large_s2
    [1, 4, 4, 8]
    >>> large_s3 = insert_items(large_s2, 4, 6)
    >>> large_s3
    [1, 4, 6, 4, 6, 8]
    >>> large_s3 is large_s
    True
    """
    idx = 0
    while idx<len(s):
        if s[idx]==before:
            s.insert(idx+1,after)
            idx+=1
        idx+=1
    return s

注意:

  • before==after时,可能会一直在相同元素后添加相同元素,导致死循环
  • 需要在每次相等时手动让idx向后移动一位
  • 且需要判断长度防止死循环

Q3: Group By

实现函数,传入列表s与函数fn,返回一个字典

字典的值为s的元素,键为fn(e),且对相同fn(e)的的元素在同一个列表中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def group_by(s, fn):
    """Return a dictionary of lists that together contain the elements of s.
    The key for each list is the value that fn returns when called on any of the
    values of that list.

    >>> group_by([12, 23, 14, 45], lambda p: p // 10)
    {1: [12, 14], 2: [23], 4: [45]}
    >>> group_by(range(-3, 4), lambda x: x * x)
    {9: [-3, 3], 4: [-2, 2], 1: [-1, 1], 0: [0]}
    """
    grouped = {}
    for val in s:
        key = fn(val)
        if key in grouped:
            grouped[key].append(val)
        else:
            grouped[key] = [val]
    return grouped

遍历s,计算key值,若存在则直接添加到key对应val的列表中,若无则创建列表并填入该值

Q5: Count Occurrences

实现一个函数,传入一个迭代器t,整数n和值x,返回前n个t中和x相等的的数值的个数

注意:在t上调用next n次,确保至少在t中有n个元素

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 count_occurrences(t, n, x):
    """Return the number of times that x is equal to one of the
    first n elements of iterator t.

    >>> s = iter([10, 9, 10, 9, 9, 10, 8, 8, 8, 7])
    >>> count_occurrences(s, 10, 9)
    3
    >>> t = iter([10, 9, 10, 9, 9, 10, 8, 8, 8, 7])
    >>> count_occurrences(t, 3, 10)
    2
    >>> u = iter([3, 2, 2, 2, 1, 2, 1, 4, 4, 5, 5, 5])
    >>> count_occurrences(u, 1, 3)  # Only iterate over 3
    1
    >>> count_occurrences(u, 3, 2)  # Only iterate over 2, 2, 2
    3
    >>> list(u)                     # Ensure that the iterator has advanced the right amount
    [1, 2, 1, 4, 4, 5, 5, 5]
    >>> v = iter([4, 1, 6, 6, 7, 7, 6, 6, 2, 2, 2, 5])
    >>> count_occurrences(v, 6, 6)
    2
    """
    cnt = 0
    times = 0
    while cnt<n:
        if next(t)== x:
            times+=1
        cnt+=1
    return times

注意提前跳出循环一次,防止迭代器到尾后返回traceback

Q6: Repeated

实现函数,传入迭代器t与一个大于1的整数k,返回t中第一个连续出现k次的元素

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 repeated(t, k):
    """Return the first value in iterator t that appears k times in a row,
    calling next on t as few times as possible.

    >>> s = iter([10, 9, 10, 9, 9, 10, 8, 8, 8, 7])
    >>> repeated(s, 2)
    9
    >>> t = iter([10, 9, 10, 9, 9, 10, 8, 8, 8, 7])
    >>> repeated(t, 3)
    8
    >>> u = iter([3, 2, 2, 2, 1, 2, 1, 4, 4, 5, 5, 5])
    >>> repeated(u, 3)
    2
    >>> repeated(u, 3)
    5
    >>> v = iter([4, 1, 6, 6, 7, 7, 8, 8, 2, 2, 2, 5])
    >>> repeated(v, 3)
    2
    """
    assert k > 1
    pre = 0
    cnt = 0
    while True:
        cur = next(t)
        if cur == pre:
            cnt+=1
        else:
            cnt=1
        pre = cur
        if cnt == k:
            return cur

一开始想用两个迭代器,但是不会复制

每次调用迭代器的next值,并保存前一个值,每次比较

若前后相同,则计数重复+1,否则还原为1(自身重复一次)

Q7: Sprout Leaves

传入一颗树,与新的叶子节点,使该树延伸出对应叶子节点,通过print_tree输出

t = tree(1, [tree(2), tree(3, [tree(4)])])

1
2
3
4
5
  1
 / \
2   3
    |
    4

调用sprout_leaves(t, [5, 6])后打印出树如下

1
2
3
4
5
6
7
     1
   /   \
  2     3
 / \    |
5   6   4
       / \
      5   6
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 sprout_leaves(t, leaves):
    """Sprout new leaves containing the labels in leaves at each leaf of
    the original tree t and return the resulting tree.

    >>> t1 = tree(1, [tree(2), tree(3)])
    >>> print_tree(t1)
    1
      2
      3
    >>> new1 = sprout_leaves(t1, [4, 5])
    >>> print_tree(new1)
    1
      2
        4
        5
      3
        4
        5

    >>> t2 = tree(1, [tree(2, [tree(3)])])
    >>> print_tree(t2)
    1
      2
        3
    >>> new2 = sprout_leaves(t2, [6, 1, 2])
    >>> print_tree(new2)
    1
      2
        3
          6
          1
          2
    """
    if is_leaf(t):
        return tree(label(t), [tree(leaf) for leaf in leaves])
    return tree(label(t), [sprout_leaves(s, leaves) for s in branches(t)])

Q8: Partial Reverse

实现函数,传入列表s与起始元素start,将start到列表末尾的元素反转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def partial_reverse(s, start):
    """Reverse part of a list in-place, starting with start up to the end of
    the list.

    >>> a = [1, 2, 3, 4, 5, 6, 7]
    >>> partial_reverse(a, 2)
    >>> a
    [1, 2, 7, 6, 5, 4, 3]
    >>> partial_reverse(a, 5)
    >>> a
    [1, 2, 7, 6, 5, 3, 4]
    """
    end = len(s)-1
    while start < end:
        s[start], s[end] = s[end], s[start]
        start+=1
        end-=1

使用序列解包,交换两个元素可以通过a,b=b,a实现

Lab 6

Q1: Bank Account

扩充Account类,添加属性transaction,即一个transaction列表实例,每一次调用都会创建一个该实例,记录每次调用depositwithdraw前后的余额。另外,每个transaction有一个id属性,记录之前账户对depositwithdraw的调用次数。id对于每个账户内是唯一的,而不是在所有transaction中的全局标识符

transaction有两个方法:

  • changed:若在调用前后balance发生变化则返回True,否则返回False
  • report:返回一个字符串用来描述该次transaction,以id开始,之后返回消息
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
class Transaction:
    def __init__(self, id, before, after):
        self.id = id
        self.before = before
        self.after = after

    def changed(self):
        """Return whether the transaction resulted in a changed balance."""
        if self.before == self.after:
            return False
        else :
            return True

    def report(self):
        """Return a string describing the transaction.

        >>> Transaction(3, 20, 10).report()
        '3: decreased 20->10'
        >>> Transaction(4, 20, 50).report()
        '4: increased 20->50'
        >>> Transaction(5, 50, 50).report()
        '5: no change'
        """
        msg = 'no change'
        if self.changed():
            if self.before > self.after:
                msg = 'decreased ' + str(self.before)+'->'+str(self.after)
            else :
                msg = 'increased ' + str(self.before) + '->' + str(self.after)
        return str(self.id) + ': ' + msg

class Account:
    """A bank account that tracks its transaction history.

    >>> a = Account('Eric')
    >>> a.deposit(100)    # Transaction 0 for a
    100
    >>> b = Account('Erica')
    >>> a.withdraw(30)    # Transaction 1 for a
    70
    >>> a.deposit(10)     # Transaction 2 for a
    80
    >>> b.deposit(50)     # Transaction 0 for b
    50
    >>> b.withdraw(10)    # Transaction 1 for b
    40
    >>> a.withdraw(100)   # Transaction 3 for a
    'Insufficient funds'
    >>> len(a.transactions)
    4
    >>> len([t for t in a.transactions if t.changed()])
    3
    >>> for t in a.transactions:
    ...     print(t.report())
    0: increased 0->100
    1: decreased 100->70
    2: increased 70->80
    3: no change
    >>> b.withdraw(100)   # Transaction 2 for b
    'Insufficient funds'
    >>> b.withdraw(30)    # Transaction 3 for b
    10
    >>> for t in b.transactions:
    ...     print(t.report())
    0: increased 0->50
    1: decreased 50->40
    2: no change
    3: decreased 40->10
    """

    # *** YOU NEED TO MAKE CHANGES IN SEVERAL PLACES IN THIS CLASS ***

    def __init__(self, account_holder):
        self.balance = 0
        self.holder = account_holder
        self.counter = -1
        self.transactions = []

    def id_gene(self):
        self.counter+=1
        yield self.counter

    def deposit(self, amount):
        """Increase the account balance by amount, add the deposit
        to the transaction history, and return the new balance.
        """
        pre = self.balance
        self.balance = self.balance + amount
        self.transactions.append(Transaction(next(self.id_gene()),pre, self.balance))
        return self.balance

    def withdraw(self, amount):
        """Decrease the account balance by amount, add the withdraw
        to the transaction history, and return the new balance.
        """
        if amount > self.balance:
            self.transactions.append(Transaction(next(self.id_gene()), self.balance, self.balance))
            return 'Insufficient funds'
        pre = self.balance
        self.balance = self.balance - amount
        self.transactions.append(Transaction(next(self.id_gene()), pre, self.balance))
        return self.balance

Q2: Email

一个邮箱系统包括三个类:Email, Server, Client

Client类可以compose邮件,该邮件会被sendServer类中

Server类会把该邮件运到对应Clientinbox中,为了实现这个,一个Server类拥有一个字典clients,将Email中的recipient_name与拥有该名称的Client对象绑定

注意:client永远不会更改其使用的server

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Email:
    """An email has the following instance attributes:

        msg (str): the contents of the message
        sender (Client): the client that sent the email
        recipient_name (str): the name of the recipient (another client)
    """
    def __init__(self, msg, sender, recipient_name):
        self.msg = msg
        self.sender = sender
        self.recipient_name = recipient_name

class Server:
    """Each Server has one instance attribute called clients that is a
    dictionary from client names to client objects.
    """
    def __init__(self):
        self.clients = {}

    def send(self, email):
        """Append the email to the inbox of the client it is addressed to."""
        self.clients[email.recipient_name].inbox.append(email)

    def register_client(self, client):
        """Add a client to the dictionary of clients."""
        self.clients[client.name] = client

class Client:
    """A client has a server, a name (str), and an inbox (list).

    >>> s = Server()
    >>> a = Client(s, 'Alice')
    >>> b = Client(s, 'Bob')
    >>> a.compose('Hello, World!', 'Bob')
    >>> b.inbox[0].msg
    'Hello, World!'
    >>> a.compose('CS 61A Rocks!', 'Bob')
    >>> len(b.inbox)
    2
    >>> b.inbox[1].msg
    'CS 61A Rocks!'
    >>> b.inbox[1].sender.name
    'Alice'
    """
    def __init__(self, server, name):
        self.inbox = []
        self.server = server
        self.name = name
        server.register_client(self)

    def compose(self, message, recipient_name):
        """Send an email with the given message to the recipient."""
        email = Email(message, self, recipient_name)
        self.server.send(email)

这里的inbox存储的是每一个email实例,故传参时应传入self

Q3: Make Change

实现make_change函数,输入amount和coins字典

coins字典的键是一个正整数,表示面额;值也是一个正整数,表示硬币数量,如{1:4, 5:2}表示四个便士和两个五分

函数返回列表,其中元素是求和能达到amount数量的硬币列表,其中每种面额k最多使用coins[k]

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
40
41
def make_change(amount, coins):
    """Return a list of coins that sum to amount, preferring the smallest coins
    available and placing the smallest coins first in the returned list.

    The coins argument is a dictionary with keys that are positive integer
    denominations and values that are positive integer coin counts.

    >>> make_change(2, {2: 1})
    [2]
    >>> make_change(2, {1: 2, 2: 1})
    [1, 1]
    >>> make_change(4, {1: 2, 2: 1})
    [1, 1, 2]
    >>> make_change(4, {2: 1}) == None
    True

    >>> coins = {2: 2, 3: 2, 4: 3, 5: 1}
    >>> make_change(4, coins)
    [2, 2]
    >>> make_change(8, coins)
    [2, 2, 4]
    >>> make_change(25, coins)
    [2, 3, 3, 4, 4, 4, 5]
    >>> coins[8] = 1
    >>> make_change(25, coins)
    [2, 2, 4, 4, 5, 8]
    """
    if not coins:
        return None
    smallest = min(coins)
    rest = remove_one(coins, smallest)
    if amount < smallest:
        return None
    elif amount == smallest:
        return [smallest]
    else:
        temp = make_change(amount-smallest, rest)
        if not temp:
            return make_change(amount, rest)
        else :
            return [smallest]+temp

首先尝试使用最小面额的coin,但若无法实现则尝试使用更大面额

具体步骤:

  • 进入函数默认使用一个最小面额硬币

  • amount==samllest:最小面额硬币即可满足需求,返回包含该值的列表

  • 否则,递归调用make_change(amount-smallest, rest),减去一次最小面额

  • 若返回一个列表,则将该最小面额放入列表,并继续递归调用,将其返回的列表接在后面

  • 若返回None,则表明使用最小面额无法实现,故递归调用make_change(amount, rest),让函数把最小面额硬币去除后使用更大面额尝试

Q4: Change Machine

完成ChangeMachine类中的change方法,每个该类的实例包含了一些coins,一开始都是便士。change方法传入正整数coin,把他添加到coins中,并返回列表,其中的硬币面额之和要达到cin

该类倾向于使用尽可能多的最小面额硬币

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class ChangeMachine:
    """A change machine holds a certain number of coins, initially all pennies.
    The change method adds a single coin of some denomination X and returns a
    list of coins that sums to X. The machine prefers to return the smallest
    coins available. The total value in the machine never changes, and it can
    always make change for any coin (perhaps by returning the coin passed in).

    The coins attribute is a dictionary with keys that are positive integer
    denominations and values that are positive integer coin counts.

    >>> m = ChangeMachine(2)
    >>> m.coins == {1: 2}
    True
    >>> m.change(2)
    [1, 1]
    >>> m.coins == {2: 1}
    True
    >>> m.change(2)
    [2]
    >>> m.coins == {2: 1}
    True
    >>> m.change(3)
    [3]
    >>> m.coins == {2: 1}
    True

    >>> m = ChangeMachine(10) # 10 pennies
    >>> m.coins == {1: 10}
    True
    >>> m.change(5) # takes a nickel & returns 5 pennies
    [1, 1, 1, 1, 1]
    >>> m.coins == {1: 5, 5: 1} # 5 pennies & a nickel remain
    True
    >>> m.change(3)
    [1, 1, 1]
    >>> m.coins == {1: 2, 3: 1, 5: 1}
    True
    >>> m.change(2)
    [1, 1]
    >>> m.change(2) # not enough 1's remaining; return a 2
    [2]
    >>> m.coins == {2: 1, 3: 1, 5: 1}
    True
    >>> m.change(8) # cannot use the 2 to make 8, so use 3 & 5
    [3, 5]
    >>> m.coins == {2: 1, 8: 1}
    True
    >>> m.change(1) # return the penny passed in (it's the smallest)
    [1]
    >>> m.change(9) # return the 9 passed in (no change possible)
    [9]
    >>> m.coins == {2: 1, 8: 1}
    True
    >>> m.change(10)
    [2, 8]
    >>> m.coins == {10: 1}
    True

    >>> m = ChangeMachine(9)
    >>> [m.change(k) for k in [2, 2, 3]]
    [[1, 1], [1, 1], [1, 1, 1]]
    >>> m.coins == {1: 2, 2: 2, 3: 1}
    True
    >>> m.change(5) # Prefers [1, 1, 3] to [1, 2, 2] (more pennies)
    [1, 1, 3]
    >>> m.change(7)
    [2, 5]
    >>> m.coins == {2: 1, 7: 1}
    True
    """
    def __init__(self, pennies):
        self.coins = {1: pennies}

    def change(self, coin):
        """Return change for coin, removing the result from self.coins."""
        self.coins[coin] = self.coins.get(coin, 0)+1
        result = make_change(coin, self.coins)
        for c in result:
            self.coins = remove_one(self.coins, c)
        return result
  • 将加入的coin放入coins字典,注意,这里的键为面额对应数量,而值为面额,故我们用get方法获取传入coin的键,并在此基础上+1(我们可以在第二个参数设置默认值,当没有对应面额时,get会返回设定值而非None)
  • 之后调用make_change方法,获得最小面额能实现达到coin数的硬币列表
  • 使用remove_one将类中已经用过的硬币移除
  • 返回列表

Attributes

Class Attributes

语法:

1
2
class <name>:
    <suite>
class声明将会创建一个新类并将其绑定到<name>上,作为当前环境的第一个作用域 类语句内部,赋值语句和def语句创建类属性,此时它们作为类属性放入类中,而非在作用域中 当class声明执行时,内部语句才会执行

类属性在类的所有实例之间共享 这样我们不必在每个创建的实例中存储此值,而只需要在类中存储一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Account:

    interest = 0.02 # 类属性

    def __init__(self, account_holder):
        self.balance = 0
        self.holder = account_holder

    def deposit(self, amount):
        self.balance = self.balance + amount
        return self.balance  
tom = Account('Tom')
jim = Account('Jim')
print(tom.interest)
print(jim.interest)
0.02
0.02

Attribute Lookup

By Name

表达式提供对象,名称提供要查找的属性名称 但通过name查找将会查找多个位置,这取决于点表达式: - 首先执行点左侧表达式,生成该对象 - <name>与该对象的实例属性匹配,如果存在该名称的属性,则返回值 - 如果没有找到,则在该对象的类中查找(类属性) - 返回的值是整个表达式的值,除非是一个函数,函数将会返回一个绑定的方法,对象填充为self

By Function

使用getatter函数,可以用属性名的字符串来查找对应属性 使用hasatter函数,可以判断是否具有该属性 getatter函数与点表达式查询方式相同 在对象中查询属性可能会返回实例属性或类属性之一

Attribute Assignment

左侧有点表达式的赋值语句会影响点表达式对应对象的属性 - 如果对象是一个实例,则赋值将设置一个实例属性 - 如果对象是一个类,则赋值将设置一个类属性

1
2
tom.interest = 0.08
Account.interest = 0.04

第一个语句将会在tom实例中寻找interest属性,没有找到,则在其中添加该属性 第二个语句将会在Account类中寻找interest属性,找到后将值修改为对应值

Method Calls

方法可使用点表达式调用 其中表达式可以是任何有效的python表达式 表达式的值是通过该表达式值对象中查找<name>而计算的属性值

1
tom.deposit(10)

Bound Methods

Terminlology

  • 所有对象都有属性,均为name-value二元组
  • 类是对象的一种, 故类也有属性
  • 实例属性:一个实例的属性
  • 类属性:类本身的属性,但仍可以从实例访问
  • python中函数也是对象
  • 绑定方法也是对象,即第一个参数为self的函数,该参数已经绑定到了一个实例
  • 点表达式执行那些类属性是函数的绑定方法

Methods and Functions

python区分方法与函数 - 函数是从一开始就定义的 - 绑定方法在方法被调用时将一个函数和其对象绑定起来, 即Object+Function = Bound Method

1
2
print(type(Account.deposit))
print(type(tom.deposit))
<class 'function'>
<class 'method'>

此时我们有两种方法调用 1. 作为函数调用,此时需要传递self和amount两个参数 2. 作为绑定方法调用,此时只需要传递amount参数

1
2
Account.deposit(tom, 1001)
tom.deposit(1007)
2008

Objects

OOP(Object-Oriented Programming)

类(class)

定义了某种类型的多个对象该如何表现 ### 对象(object) 类的一个实例,决定该类的类型 ### 方法(method) 在对象上调用的函数,使用点(.)调用 注意 方法不同于函数在于方法特定于对应对象 ### 蕴含理念 将复杂的大型程序组织成小的模块化组件 - 使用数据抽象 - 捆绑多个信息与相关行为 使用分散状态的计算 - 每个对象有自己的本地状态 - 每个对象也知道如何管理自己的本地状态 - 使用方法与对象交互 - 几个对象可能属于一个共同的实例 - 不同的类可以相互关联

例:Lists

内置的list函数其实是一个类 在可迭代对象上调用list可以创建一个list类的新实例 list类定义了有关list的各种用法: - 方法:append, extend, insert ,etc. - 加法与乘法运算 - 元素查找与赋值

1
2
3
print(list)
s = list(range(3))
print(type(s))
<class 'list'>
<class 'list'>

Class Statement

一个类描述了其实例的行为 例如,我们定义一个银行账户类,实现的功能如下:

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
class Account:   # 一个类以class开头,并给他命名
    
    def __init__(self, account_holder):
        '''
        定义创建类的新实例时会发生什么,即给予对象属性
        一般使用方法名__init__来构造一个类的实例,调用时自动执行, 该方法有时也被称为构造方法
        将新实例称为self,并给它分配属性,其中,account_holder是调用时传入的内容
        '''
        self.balance = 0
        self.holder = account_holder

    def deposit(self, amount):
        '''
        deposit方法调用两个参数
        self: self是该方法被调用时的类的实例
        amount:添加的余额
        '''
        self.balance = self.balance + amount
        return self.balance   # 出现输出时的情况,若没有return则返回None

    def withdraw(self, amount):
        if amount > self.balance:   # 引用balance时要加.,因为其是对象的一个属性
            return 'Insufficient funds'
        self.balance = self.balance - amount
        return self.balance

Creating Instances

Object Construction

当一个类被调用时: - 创建一个该类的新的实例 - __init__方法被调用,其中以新对象作为其中的第一个元素, 一般命名为self, 并带有在调用表达式中的任何额外参数

1
2
3
a = Account('Alan')
print(a.holder)
print(a.balance)
Alan
0

Instance Attributes

对象的属性可以通过点号表达式(.)来访问与更改 若属性之前不存在, 也可以为其赋值, 可以在任何时候增加新的属性

1
2
3
4
5
a.balance = 12   # 已存在的属性可以被更改
b = Account('Ada')
b.balance = 20
a.backup = b   # 新属性可以在任何时候添加
print(a.backup.balance)
20

Object Identity

每个用户定义类的对象都有一个唯一的标识 标识运算符isis not可以判断两个表达式是否是相同的对象 使用赋值将一个对象绑定到一个新名称时不会创建一个新对象

1
2
3
4
print(a is a)
print(a is not b)
c = a
print(a is c)
True
True
True

Methods

Invoking Methods

调用方法意味着用点表达式定义类中定义的一个方法 所有调用方法都通过self变量访问对象,所以他们可以访问与操作对象的属性 如deposit方法中定义了两个参数, 但点表达式会自动为方法提供第一个参数

1
2
tom = Account('Tom')
print(tom.deposit(100))  # 调用时只需要一个参数, 方法会自动绑定到self(这里是tom)
100

Homework 4

Sequence

Q1: Deep Map

写一个函数deep_map,使得传入的列表s(s可能是嵌套列表)中的每一个元素替换为该元素传入函数f后的返回值

每一个元素包括嵌套列表中的每一个元素

提示:type(a)==list会返回True如果a是列表

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 deep_map(f, s):
    """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
    """

    for i in range(len(s)):
        if type(s[i])==list:
            deep_map(f,s[i])
        else:
            s[i]=f(s[i])

遍历列表下标i,如果发现内部有嵌套列表,就递归进入遍历嵌套列表,若当前位置为一般元素,则调用函数并将结果替换

Data Abstraction

要用到的ADT

Mobile:一种悬挂雕像

  • 一个mobile一定有一个左arm和一个右arm
  • 一个arm有一个正数大小的长度,且尾端一定挂着一个mobile或一个planet
  • 一个planet有一个正数大小的质量,且没有东西挂在上面

Q2: Mass

补充完成构造器planet与选择器mass

使得每一格planet用一个二元列表表示:['planet', mass]

其中total_mass函数可以用来计算一个planet或一个mobile的质量

1
2
3
4
5
6
7
8
9
def planet(mass):
    """Construct a planet of some mass."""
    assert mass > 0
    return ['planet', mass]

def mass(p):
    """Select the mass of a planet."""
    assert is_planet(p), 'must call mass on a planet'
    return p[1]

按照提示构造即可

Q3: Balanced

实现balanced函数,判断m是否是一个”balanced mobile”

“balanced mobile”的定义如下:

  • 左arm的扭矩等于右arm的扭矩,其中扭矩为左arm的长度与挂在左arm上的质量之积
  • 挂在arm尾端的mobile自身为balanced mobile,planet自身也是balanced的

提示:使用total_mass函数,选择器函数,不要逾越ADT的屏障

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 balanced(m):
    """Return whether m is balanced.

    >>> t, u, v = examples()
    >>> balanced(t)
    True
    >>> balanced(v)
    True
    >>> p = mobile(arm(3, t), arm(2, u))
    >>> balanced(p)
    False
    >>> balanced(mobile(arm(1, v), arm(1, p)))
    False
    >>> balanced(mobile(arm(1, p), arm(1, v)))
    False
    >>> from construct_check import check
    >>> # checking for abstraction barrier violations by banning indexing
    >>> check(HW_SOURCE_FILE, 'balanced', ['Index'])
    True
    """
    if is_planet(m):
        return True
    else:
        torque_left = length(left(m))*total_mass(end(left(m)))
        torque_right = length(right(m))*total_mass(end(right(m)))
        return torque_left == torque_right and balanced(end(left(m))) and balanced(end(right(m)))

这个结构类似树,我们可以递归的求解这个问题

  • Base case:若传入的是一个planet,则返回True,因为planet在这个结构中类似叶子节点,是整个mobile的终点
  • 递归:每次需要比较mobile的左右扭矩是否相等,同时递归比较左右arm下的左右扭矩是否相等,直到递归到planet直接返回True

Trees

Q4: Maximum Path Sum

写一个函数,返回树上从根节点到叶子节点的所有路径中节点权值总和最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def max_path_sum(t):
    """Return the maximum root-to-leaf path sum of a tree.
    >>> t = tree(1, [tree(5, [tree(1), tree(3)]), tree(10)])
    >>> max_path_sum(t) # 1, 10
    11
    >>> t2 = tree(5, [tree(4, [tree(1), tree(3)]), tree(2, [tree(10), tree(3)])])
    >>> max_path_sum(t2) # 5, 2, 10
    17
    """
    max_n = -1
    if is_leaf(t):
        return label(t)

    for b in branches(t):
        max_n=max(max_path_sum(b),max_n)

    return max_n+label(t)

使用递归遍历

先直接深入到叶子节点,发现叶子节点则返回节点权值,再往前返回,每上一个节点返回已经求和的值+当前节点权值,且每次遍历取当前不同路径的最大值