介绍了邻接表,一种很有效率的存图方法
【CS61A】CS61A——Homework 5
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)]+pIf our current label is equal to
value, we’ve found a path from the root to a node containingvaluecontaining 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 containingvalue. 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_pathson 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.
【CS61A】CS61A——Lab5
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 61
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实现
【CS61A】CS61A——Lab6
Lab 6
Q1: Bank Account
扩充Account类,添加属性transaction,即一个transaction列表实例,每一次调用都会创建一个该实例,记录每次调用deposit和withdraw前后的余额。另外,每个transaction有一个id属性,记录之前账户对deposit或withdraw的调用次数。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.balanceQ2: Email
一个邮箱系统包括三个类:Email, Server,
Client
Client类可以compose邮件,该邮件会被send到Server类中
Server类会把该邮件运到对应Client的inbox中,为了实现这个,一个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将类中已经用过的硬币移除 - 返回列表
【CS61A】CS61A——Attributes
Attributes
Class Attributes
语法: 1
2class <name>:
<suite><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
【CS61A】CS61A——Objects
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.balanceCreating 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
每个用户定义类的对象都有一个唯一的标识
标识运算符is与is 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
【CS61A】CS61A——Homework 4
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)使用递归遍历
先直接深入到叶子节点,发现叶子节点则返回节点权值,再往前返回,每上一个节点返回已经求和的值+当前节点权值,且每次遍历取当前不同路径的最大值
【CS61A】CS61A——Generators
Generators
Generator Functions
- 生成器函数是用来生成(yields)值而非返回值的函数
- 生成器函数使用yield关键字返回生成的值
- 生成器可以多次生成值,而普通函数只能返回一次值
- 生成器是调用生成器函数时自动创建的迭代器
- 调用生成器函数,它会返回一个生成器来迭代该函数生成的值
- 当函数执行到yield时,生成的值将会作为下次迭代器的数值,而此时执行在yield处执行,但会记住当前环境,以便下次继续执行 e.g.
1
2
3
4
5
6
7
8
9
10
def evens(start, end):
even = start + (start%2)
while even<end:
yield even
even+=2
if __name__ == "__main__":
t = evens(2,10)
for i in t:
print(i)2
4
6
8
Generators & Iterators
yield from语句 yield from语句允许从一个迭代器或可迭代变量中生成所有值 两个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def countdown(k):
if k>0:
yield k
yield from countdown(k-1)
'''
等价于
for i in countdown(k-1):
yield i
'''
else:
yield 'Blast off'
if __name__ == "__main__":
t = countdown(3)
for i in t:
print(i)3
2
1
Blast of
1
2
3
4
5
6
7
8
9
10
11
def prefixes(s):
if s:
yield from prefixes(s[:-1])
yield s
def substrings(s):
if s:
yield from prefixes(s)
yield from substrings(s[1:])
if __name__ == "__main__":
print(list(prefixes("both")))
print(list(substrings("top")))['b', 'bo', 'bot', 'both']
['t', 'to', 'top', 'o', 'op', 'p']
Example: Partitions
详见递归时的数字分割例子 使用列表:
1
2
3
4
5
6
7
8
9
10
11
12
13
def partitions(n,m):
if n<0 or m==0:
return []
else:
exact_match = []
if n==m:
exact_match = [str(m)]
with_m = [p+'+'+str(m) for p in partitions(n-m,m)]
without_m = partitions(n,m-1)
return exact_match+with_m+without_m
if __name__ == "__main__":
for p in partitions(6,4):
print(p)2+4
1+1+4
3+3
1+2+3
1+1+1+3
2+2+2
1+1+2+2
1+1+1+1+2
1+1+1+1+1+1
使用生成器: 使代码更优雅,而且可以输出指定个数的可能结果
1
2
3
4
5
6
7
8
9
10
11
def partitions(n,m):
if n>0 and m>0:
if n==m:
yield str(m)
for p in partitions(n-m,m):
yield p+'+'+str(m)
yield from partitions(n,m-1)
if __name__ == "__main__":
t = partitions(6,4)
for p in range(5):
print(next(t))2+4
1+1+4
3+3
1+2+3
1+1+1+3
【数据结构】队列
介绍了多种队列的原理与实现,以及单调队列
【CS61A】CS61A——Iterators
Iterators
Iterators
迭代器是一种常见的接口,python中常用作一种访问不同容器元素的方式 -
容器可以提供一个迭代器,以按照某种顺序访问容器内元素 -
iter(iterable):创建迭代器,接受任何可迭代的东西,返回可迭代元素的迭代器
- next(iterator):推进迭代器,返回迭代器的下一个元素 -
不同迭代器可以迭代相同值,但它们彼此之间是独立的 -
使用list,tuple或sorted函数可以查看一个迭代器的剩余元素 -
当迭代器到达末尾,Python会返回一个StopIteration的异常 -
所有迭代器都是可变对象
1
2
3
4
5
6
7
8
s = [[1,2],3,4,5]
t = iter(s)
u = iter(s)
print("t:",next(t))
print("u:",next(u))
print("t:",next(t))
list(t)
print(next(t))t: [1, 2]
u: [1, 2]
t: 3
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In[4], line 8
6 print("t:",next(t))
7 list(t)
----> 8 print(next(t))
StopIteration:
Dictionary Itertion
字典的键,值以及键值对都是可遍历的 ,可以生成迭代器 字典中的键值对顺序取决于它们的添加顺序(Python3.6+) 迭代各项目的方法如下
1
2
3
4
5
6
7
8
9
10
11
d = {'one':1, 'two':2, 'three':3}
d['zero'] = 0
# 遍历键
k = iter(d.keys()) # iter(d)
print(next(k))
# 遍历值
v = iter(d.values())
print(next(v))
# 遍历键值对
i = iter(d.items()) # 以元组形式迭代
print(next(i))one
1
('one', 1)
注意:若在迭代器创建后,字典发生了结构上的改变(增加,减少元素等),迭代器会失效。若只是改变键对应的值则不会。
For Statement
for循环可以遍历迭代器本身,可以从迭代器当前位置遍历到迭代器末尾,但这会推进迭代器,故不能重复使用 而可迭代对象可以从头到尾遍历多次
1
2
3
4
5
6
r = range(3,6)
ri = iter(r)
for i in ri:
print(i)
for i in ri:
print(i)3
4
5
Built-in Function for Iteration
许多python内置的序列操作使用迭代器作为返回值,以惰性方式计算
惰性计算意味着只有被请求时才计算结果 e.g.
map(func, iterable):接受一个函数与一个可迭代对象,将该函数应用于可迭代对象中的每一个元素,返回一个迭代器遍历可迭代对象中所有x在func下的值
filter(func, iterable):接受一个断言函数与一个可迭代对象,返回一个迭代器遍历可迭代对象中所有在func下为真的x
zip(first_iter, second_iter):接受两个可迭代对象,返回迭代器遍历相同索引的(x,y)对
reversed(sequence):接受一个序列,返回迭代器反向遍历该序列
1
2
3
4
5
bcd = ['b', 'c', 'd']
m = map(lambda x: x.upper(), bcd) # 返回一个迭代器
print(next(m))
print(next(m))
print(next(m))B
C
D
1
2
3
4
5
6
7
def double(x):
print(x, '=>',2*x)
return 2*x
m = map(double, range(3,7))
f = lambda y: y>=10
t = filter(f,m) # 仅遍历满足f函数下的返回值
list(filter(f,m)) # 将所有可能的返回值存入列表3 => 6
4 => 8
5 => 10
6 => 12
[10, 12]
Zip
zip函数返回迭代器,遍历相同索引的值组成的元组 若一个可迭代对象比另一个长,zip会跳过多余的 zip可以接受多个可迭代对象
1
2
3
4
5
print(list(zip([1,2],[3,4])))
print(list(zip([1,2],[3,4,5])))
print(list(zip([1,2],[3,4,5], [6,7])))[(1, 3), (2, 4)]
[(1, 3), (2, 4)]
[(1, 3, 6), (2, 4, 7)]
e.g. 检测任意一个序列是否为回文序列,使用zip
1
2
3
4
5
6
7
8
9
10
11
def palindrome(s):
# >>> palindrome([3,1,4,1,5])
# False
# >>> palindrome([3,1,4,1,3])
# True
# >>> palindrome('seveneves')
# True
return all([a==b for a,b in zip(s,reversed(s))])
if __name__ == '__main__':
print(palindrome([3,1,4,1,3]))
print(palindrome('seveneves'))True
True
Using Iterators
使用迭代器的代码对数据本身更改不大 - 当数据表示形式改变时,使用迭代器可以不必重写代码 - 其他人更可能在他们的数据上使用你的代码 迭代器将序列中的元素与所在位置绑定 - 将对象传递给其他函数时始终保留着位置 - 可以确保序列中的每个元素只执行一次 - 传递迭代器限制了对序列执行的操作,即只能请求下一个值