仅用作个人学习记录
Reference:https://inst.eecs.berkeley.edu/~cs61a
Multiple Variables
x, y = (1, 9)
x, y = (2, 2)
...
在 Python 中,我们常见到上面这种写法,此时等号右边一定是某种序列,且序列的元素个数与左边相等。就像我们期望的那样,序列的第一个值赋给 x,第二个值赋给 y。
>>> L = [ (1, 9), (2, 2), (5, 6), (3, 3) ]
>>> same = 0
>>> for x, y in L:
... if x == y:
... same += 1
>>> same
2
Two Iterations at Once
zip
是 Python 内置的函数,它将两个列表压缩在一起,返回一个generator
,这个会在后续讲到。
zip 函数可以组合多个序列:
>>> list(zip([1, 2, 5, 3], [9, 2, 6, 3, 10]))
[(1, 9), (2, 2), (5, 6), (3, 3)]
>>> # Length of result is that of shortest sequence
>>> list(zip([1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12, 15]))
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12)]
如果传递给 zip 的两个序列的长度不一致,会以最小长度为准。
>>> beasts = ["aardvark", "axolotl", "gnu", "hartebeest"]
>>> for n, animal in zip(range(1, 5), beasts):
... print(n, animal)
1 aardvark
2 axolotl
3 gnu
4 hartebeest
Modifying Lists
>>> L = [1, 2, 3, 4, 5]
>>> L[2] = 6
>>> L
[1, 2, 6, 4, 5]
>>> L[1:3] = [9, 8]
>>> L
[1, 9, 8, 4, 5]
>>> L[2:4] = [] # Deleting elements
>>> L
[1, 9, 5]
>>> L[1:1] = [2, 3, 4, 5] # Inserting elements
>>> L
[1, 2, 3, 4, 5, 9, 5]
>>> L[len(L):] = [10, 11] # Appending
>>> L
[1, 2, 3, 4, 5, 9, 5, 10, 11]
>>> L[0:0] = range(-3, 0) # Prepending
>>> L
[-3, -2, -1, 1, 2, 3, 4, 5, 9, 5, 10, 11]
当然上述这些操作可以用内置的方法完成,但那涉及到面向对象的思想,会在后续介绍。
List Comprehensions
[ <map expression> for <var> in <sequence expression> if <filter expression> ]
更短的版本,选择不过滤:
[ <map expression> for <var> in <sequence expression>
遍历sequence
,如果该元素使得filter expression
为真,则执行map expression
并将结果按序放入要返回的列表中。
执行过程:
- 在当前环境下创建一个新的局部环境运行
List Comprehensions
。 - 创建一个空
list
用来存放map expression
的值。 - 遍历
sequence
,若使得filter expression
为真,计算map expression
,并将其放入list
。如果没有if
内容直接执行map expression
。 - 返回
list
。
>>> [ (a, b) for a in range(10, 13) for b in range(2) ]
[(10, 0), (10, 1), (11, 0), (11, 1), (12, 0), (12, 1)]
>>> [(a, b) for a in range(4) for b in range(a, 4)]
[(0, 0), (0, 1), (0, 2), (0, 3), (1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]
通过上面两个例子,可以发现嵌套for
语句的执行顺序就如同C语言中的嵌套循环。
>>> [0 for i in range(5)]
[0, 0, 0, 0, 0]
>>> [0 for _ in range(5)]
[0, 0, 0, 0, 0]
常见的惯例是,将单下划线字符用于for
头部,如果这个名称在语句组中不会使用。要注意对解释器来说,下划线只是另一个名称,但是在程序员看来中具有固定含义,它表明这个名称不应出现在任何表达式中。
Exercise I
def matches(a, b):
"""Return the number of values k such that A[k] == B[k].
>>> matches([1, 2, 3, 4, 5], [3, 2, 3, 0, 5])
3
>>> matches("abdomens", "indolence")
4
>>> matches("abcd", "dcba")
0
>>> matches("abcde", "edcba")
1
"""
#return sum([1 for i in range(len(a)) if a[i] == b[i]]) # error when len(a) != len(b)
return sum([1 for x, y in zip(a, b) if x == y])
Exercise II
def triangle(n):
"""Assuming N >= 0, return the list consisting of N lists:
[1], [1, 2], [1, 2, 3], ... [1, 2, ... N].
>>> triangle(0)
[]
>>> triangle(1)
[[1]]
>>> triangle(5)
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]]
"""
return [list(range(1, i + 1)) for i in range(1, n + 1)]
Data Abstraction
数据抽象允许我们像操作基本数据单元那样操作复合值。
数据抽象是一种方法论,使我们将复合数据对象的使用细节与它的构造方式隔离。
抽象数据类型 (ADT)(如上一讲中的对 pair 抽象)表示某种事物及其上的操作。
对于每种类型,我们定义一个接口,为该类型数据的用户描述可用的操作(Application Programmer’s Interface–API)。通常,接口由函数组成。这些函数的规范(句法和语义)的集合构成了类型的规范。
其中一些功能属于常见类别:
-
Constructors:创建一个新的类型实例
-
Accesors:返回类型实例的属性
-
Mutators:修改类型实例
Rational Numbers
def make_rat(n, d):
"""The rational number N/D, assuming N, D are integers, D!=0"""
g = gcd(n, d)
n //= g; d //= g
return (n, d)
def numer(r):
"""The numerator of rational number R in lowest terms."""
return r[0]
def denom(r):
"""The denominator of rational number R in lowest terms.
Always positive."""
return r[1]
def add_rat(x, y):
return make_rat(numer(x) * denom(y) + numer(y) * denom(x),
denom(x) * denom(y))
def mul_rat(x, y):
return make_rat(numer(x) * numer(y), denom(x) * denom(y))
def str_rat(r): # (For fun: a little new Python string magic)
return str(numer(r)) if denom(r) == 1 else f"{numer(r)}/{denom(r)}"
def equal_rat(x, y):
return numer(x) * denom(y) == numer(y) * denom(x)
def exact_harmonic_number(n):
"""Return 1 + 1/2 + 1/3 + ... + 1/N as a rational number.
>>> str_rat(exact_harmonic_number(1))
'1'
>>> str_rat(exact_harmonic_number(3))
'11/6'
>>> str_rat(exact_harmonic_number(10))
'7381/2520'
"""
s = make_rat(0, 1)
for k in range(1, n + 1):
s = add_rat(s, make_rat(1, k))
return s
Layers of Abstraction
这些黑线代表抽象壁垒。使用有理数的程序仅仅通过算术函数来操作它们:add_rat
、mul_rat
和eq_rat
。相应地,这些函数仅仅由构造器和选择器make_rat
、numer
和and denom
来实现,它们本身由元组实现。元组如何实现的字节和其它层级没有关系,只要元组支持选择器和构造器的实现。
壁垒上层不会使用到任何壁垒下层的东西。壁垒下层仅仅使用壁垒上层提供的操作而对如何实现的并不关心。
抽象壁垒使程序更易于维护和修改。当壁垒上层需要改动,只要不改变接口,则壁垒下层就无需改动。