用Python编程 笔记3

第七章 字符串(Strings)

7.1 一种复合数据类型(A compound data type)

复合数据类型是一种由许多更小的碎片所组成的类型。
根据我们的需要,我们可能会想要将复合数据类型作为一个单一整体来看待,也可能会想要获取其中某一/某几个部分。

>>> fruit = “banana”
>>> letter = fruit[1]
>>> print letter

Python中的方括号运算符被用来选取一个字符串中的一个单一字符。
看上去letter=fruit[1]的表达将选取fruit的第一个字符,并将它赋值给letter。
但是我们实际上的到的输出结果是:

a

这是因为一个字符串中的第一个字母实际上是第0位。

>>> letter = fruit[0]
>>> print letter
b

使用方括号的表达式叫做一个索引(index),在一个有序集(ordered set)中选取一个特定的编号的元素。
上例中是在一个字符串中选取一个特定位置的字母。
索引的值可以是任何整数的表达式。

7.2 长度

函数len的作用是返回一个字符串中字符的个数:

>>> fruit = “banana”
>>> len(fruit)
6

此时,字符串的最后一个字符应该是 fruit[length-1],倒数第二个字符应该是fruit[length-2]

7.3 遍历和for循环(Traversal and the for loop)

很多涉及到字符串的计算需要一次一个字符地处理,通常是从头开始,依次选取每一个字符对其进行一些计算,然后直到最后一个字符。这样的模式叫做遍历。

使用while语句可以实现一个遍历的编码:

1
2
3
4
5
index = 0
while index < len(fruit):
  letter = fruit[index]
  print letter
  index = index + 1

上面这个循环将字符串的每一个字母都分行输出出来。

Python还提供另一种语法更简单的方法:for循环

1
2
for char in fruit:
  print char

以上循环每运行一次都会将字符串中的下一个字符赋值给变量char,直到没有剩余的字符为止。

下面的例子展示的是如何使用串联/连接(concatenation)和一个for循环,生成一个字母表顺序(Abecedarian series)。Robert McCloskey的数 Make Way for Ducklings中Ducklings的名字分别为:Jack, Kack, Lack, Mack, Nack, Ouack, Pack和Quack,下面的循环将依顺序输出这些名字:

1
2
3
4
5
prefixes = "JKLMNOPQ"
suffix = "ack"
for letter in prefixes:
  print letter + suffix

输出的结果是:

Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack

7.4 字符串片段(String slices)

字符串的一段叫做一个片段。选取一个片段的方法类似于选取一个字符:

>>> s = “Peter, Paul, and Mary”
>>> print s[0:5]
Peter
>>> print s[7:11]
Paul
>>> print s[17:21]
Mary

运算符[n:m]将返回字符串的第n个至第m-1个字符,包含第n个字符,但是不包含第m个字符。

这看上去有些违反直觉,下面的图表能够帮助理解:

TLCS7-1 diagram

如果省略第一个索引n,则片段将从字符串的开头开始,如果省略第二个索引m,片段则将到字符串的结尾停止。

>>> fruit = “banana”
>>> fruit[:3]
’ban’
>>> fruit[3:]
’ana’

7.5 字符串的比较

比较运算符可以用来判断字符串是否一样,或者单词的字母表顺序:

1
2
3
4
5
6
if word < "banana":
  print "Your word," + word + ", comes before banana."
elif word > "banana":
  print "Your word," + word + ", comes after banana."
else:
  print "Yes, we have no bananas!"

要注意的是,大小写字母比较时,大写字母都排在小写字母之前(ASCII表顺序?),所以比较时通常会将要比较的字符串中的字符统一转变成大写或者统一转变成小写。

7.6 字符串是不可变的(Strings are immutable)

使用[]运算符尝试改变字符串中的字符,将会导致错误:

1
2
3
greeting = "Hello, world!"
greeting[0] = ’J’ # ERROR!
print greeting

字符串时不可变的,这意味着你无法改变一个已有的字符串。你能做的是,创造一个与原本字符串不同的新的字符串:

1
2
3
greeting = "Hello, world!"
newGreeting = ’J’ + greeting[1:]
print newGreeting

这样的解决方案是将一个新的字符连接(concatenate)到greeting这个字符串的一个片段上。
这个运算符对原本的字符串没有任何影响。

 7.7 一个查询函数

1
2
3
4
5
6
7
def find(str, ch):
  index = 0
  while index < len(str):
    if str[index] == ch:
      return index
    index = index + 1
  return -1

这里的find函数的作用是与[]运算符的作用相反的,它将查找一个字符串内是否有某一个字符并输出其所在位置。

这样的计算模式有的时候被叫做“我找到了”遍历(”eureka” traversal),当我们找到所寻找的东西时,我们大叫“Eureka!”并且停止寻找。

7.8 循环和计数

以下程序作用是计算一个字符串中,字符a出现的次数:

1
2
3
4
5
6
fruit = "banana"
count = 0
for char in fruit:
  if char == ’a’:
    count = count + 1
print count

这里的程序演示的是另一种计算模式:计数器(counter)。

7.9 字符串模块(The string module)

字符串模块含有许多实用的函数,使用之前我们需要导入模块。

>>> import string

string模块中有一个find函数,可以实现刚才编写查找功能:

>>> fruit = “banana”
>>> index = string.find(fruit, “a”)
>>> print index
1

因为使用模块的函数时,需要写全模块名.函数名,这样就便于区分自定义函数和导入的函数。

实际上,string.find有更强大的功能。

>>> string.find(“banana”, “na”)
2

可以用来查找子串

>>> string.find(“banana”, “na”, 3)
4

可以额外使用一个实际参数来指定从哪一个索引开始

>>> string.find(” bob”, “b”, 1, 2)
-1

也可以再采取一个实际参数来指定到哪一个索引为止。

7.10 字符分类 (Character classification)

String模块含有一些有用的常量:

>>> print string.lowercase
>>> print string.uppercase
>>> print string.digits

string.lowercase含有系统认为是小写字符的所有字符。

我们可以使用这些常量来将字符归类。

例如:find(lowercase, ch)返回的值如果不是-1,那么ch就一定是小写的。

1
2
def isLower(ch):
  return string.find(string.lowercase, ch) != -1

我们可以利用运算符in,它判断一个字符是否在字符串中出现了:

1
2
def isLower(ch):
  return ch in string.lowercase

我们还可以使用比较运算符:

1
2
def isLower(ch):
  return ’a’ <= ch <= ’z’

string模块中还定义有一个特殊的常量:string.whitspace。
它包含所有空格字符:空格, tab(t)和换行(n)

string模块的更多函数可以参见Python Library Reference.

第8章 列表 (lists)

  • 列表是一系列有序的值,每一个值都有用一个索引来标识。
  • 构成列表的值叫做该列表的元素。
  • 列表与字符串类似,不同点在于列表中的元素可以是任何类型的。
  • 与列表和字符串类似的,由一组有序对象构成的东西叫序列。

8.1 列表的值

构建一个列表的最简单的方式是将所有的元素都包含在方括号内:

[10, 20, 30, 40]
//四个整数构成的列表
["spam", "bungee", "swallow"]
//三个字符串构成的列表
["hello", 2.0, 5, [10, 20]]
//由字符串、整数以及另一个列表构成的列表

列表中包含列表叫做列表的嵌套。

创建包含连续整数的列表有较为简便的方法,使用range

range(n, m)生成的列表中的数从n开始,到m-1为止

>>> range(1,5)
[1, 2, 3, 4]

range(m)与range(0,m)的效果一样:

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

range(n, m, d)将生成从n开始,到m-1为止,步长为d的列表

>>> range(1, 10, 2)
[1, 3, 5, 7, 9]

[]空列表,是一个特别的列表。

还可以通过赋值的方法创建列表:

vocabulary = ["ameliorate", "castigate", "defenestrate"]
numbers = [17, 123]
empty = []
print vocabulary, numbers, empty
[’ameliorate’, ’castigate’, ’defenestrate’] [17, 123] []

8.2 列表元素的访问

列表元素的访问同样使用方括号运算符,方括号内可以是任何整数的表达式:

number[1] = 5

>>> numbers[3-2]
5

非整数表达式将返回错误

>>> numbers[1.0]
TypeError: sequence index must be integer

索引不在列表范围内将返回错误

>>> numbers[2] = 5
IndexError: list assignment index out of range

负数索引将从列表的末尾往前算,[-1]将是列表内的最后一个元素,[-2]则是倒数第二个。

通常会使用循环变量作为列表的索引:

1
2
3
4
5
6
horsemen = ["war", "famine", "pestilence", "death"]
i = 0
while i < 4:
  print horsemen[i]
  i = i + 1

这样的计算模式叫做列表遍历。

8.3 列表长度

可以使用len函数来获取一个列表的长度,将其作为循环的判断,这样就不会出现错误:

1
2
3
4
5
6
horsemen = ["war", "famine", "pestilence", "death"]
i = 0
while i < len(horsemen):
  print horsemen[i]
  i = i + 1

当列表内嵌套有其他列表的时候,嵌套的列表只算一个元素。

8.4 列表成员 (List membership)

布尔运算符in可以用来测试一个元素是否在列表中。

>>> horsemen = [’war’, ’famine’, ’pestilence’, ’death’]
>>> ’pestilence’ in horsemen
True
>>> ’debauchery’ in horsemen
False

布尔运算符not可以用来测试一个元素是否不在列表中。

>>> ’debauchery’ not in horsemen
True

8.5 列表和for循环

1
2
for VARIABLE in LIST:
  BODY

这样的语法等于:

1
2
3
4
5
i = 0
while i < len(LIST):
  VARIABLE = LIST[i]
  BODY
  i = i + 1

使用for循环更加的简洁,可以略过循环变量i的使用。

任何列表的表达式都可以用在for循环里:

1
2
3
4
5
6
for number in range(20):
  if number % 2 == 0:
    print number
for fruit in ["banana", "apple", "quince"]:
  print "I like to eat " + fruit + "s!"

8.6 列表运算(List operations)

列表的+运算

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = a + b
>>> print c
[1, 2, 3, 4, 5, 6]

列表的*运算

>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]

8.7 列表片段(List slices)

片段运算符同样对列表有效

>>> list = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’]

>>> list[1:3]
[’b’, ’c’]

>>> list[:4]
[’a’, ’b’, ’c’, ’d’]

>>> list[3:]
[’d’, ’e’, ’f’]

>>> list[:]
[’a’, ’b’, ’c’, ’d’, ’e’, ’f’]

8.8 列表是可变的

在赋值语句的左边使用方括号运算符,我们可以改变一个列表中的元素:

更改一个元素

>>> fruit = ["banana", "apple", "quince"]
>>> fruit[0] = “pear”
>>> fruit[-1] = “orange”
>>> print fruit
[’pear’, ’apple’, ’orange’]

我们可以更改连续的几个元素:

>>> list = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
>>> list[1:3] = [’x’, ’y’]
>>> print list
[’a’, ’x’, ’y’, ’d’, ’e’, ’f’]

用赋值空列表[]的方法删除一个列表中的元素:

>>> list = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
>>> list[1:3] = []
>>> print list
[’a’, ’d’, ’e’, ’f’]

通过给指定位置赋值的方式向一个列表中添加元素:

>>> list = [’a’, ’d’, ’f’]
>>> list[1:1] = [’b’, ’c’]
>>> print list
[’a’, ’b’, ’c’, ’d’, ’f’]
>>> list[4:4] = [’e’]
>>> print list
[’a’, ’b’, ’c’, ’d’, ’e’, ’f’]

8.9 列表的删除 (List deletion)

使用上面用空列表赋值的方式来删除列表中的元素不是十分可靠,使用del语句可以删除一个列表中的元素。

删除一个指定元素:

>>> a = [’one’, ’two’, ’three’]
>>> del a[1]
>>> a
[’one’, ’three’]

使用片段作为索引来删除多个元素:

>>> list = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
>>> del list[1:5]
>>> print list
[’a’, ’f’]

8.10 对象和值 (Objects and values)

对象是具有独一无二的标识符(identifier)的值,变量可以用来指向对象。

id函数可以用来获得对象的标识符:

a = “banana”
b = “banana”

>>> id(a)
135044008
>>> id(b)
135044008

上例表明当a和b都被赋值为字符串“banana”时,Python只创建了一个字符串对象并让a和b都分别指向了该对象。

列表则与字符串不一样,即便是两个看上去一样的字符串,Python会生成两个不同的对象。

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> id(a)
135045528
>>> id(b)
135041704

下面的是揭示了两者区别的图示:

8.11 别名 (Aliasing)

因为变量被用来指向对象,如果我们将一个变量赋值给另一个变量,此时两个变量就同时指向同一个对象:

>>> a = [1, 2, 3]
>>> b = a

用图表示为:

TLCS8-3

因为此时这个列表具有两个不同的名称a和b,我们将这称为使用别名(aliased)。这种情况下对一个别名(alias)的改变将影响另一个。

>>> b[0] = 5
>>> print a
[5, 2, 3]

虽然这种行为是有用的,有时也会带来意想不到的结果或者与预想相违的效果。

一般情况来说,当你使用可变对象的时候最好避免使用别名。

8.12 克隆列表 (Cloning Lists)

如果我们需要对一个列表进行修改并且同时需要保留一个原列表的复制,这样的过程叫做克隆。

使用片段操作符是最容易的实现方法:

>>> a = [1, 2, 3]
>>> b = a[:]
>>> print b
[1, 2, 3]

8.13 列表的形式参数(List parameters)

通过实际参数来传递列表实际上是传递了列表的参引(Reference),而不是复制一个新的列表。

1
2
def head(list):
  return list[0]

函数定义中list为实际参数,这个函数使用起来效果如下:

>>> numbers = [1, 2, 3]
>>> head(numbers)
1

head函数内的形式参数list和函数外的变量numbers都是同一个列表对象的别名,图示如下:

TLCS8-5

如果一个函数改变了一个列表的形式参数,则这个列表实际上也被改变了。

1
2
def deleteHead(list):
  del list[0]

使用这个函数将删除一个列表的第一个元素:

>>> numbers = [1, 2, 3]
>>> deleteHead(numbers)
>>> print numbers
[2, 3]

如果一个函数返回一个列表,则会产生一个新的列表对象,原有列表不受影响:

1
2
def tail(list):
  return list[1:]

函数tail的会产生一个新的列表,新列表含有原本列表第一个元素以外的全部元素。

>>> numbers = [1, 2, 3]
>>> rest = tail(numbers)
>>> print rest
[2, 3]

8.14 嵌套的列表(Nested lists)

访问嵌套于列表中的列表的两种方法:

>>> list = ["hello", 2.0, 5, [10, 20]]

>>> elt = list[3]
>>> elt[0]
10

>>> list[3][1]
20

8.15 矩阵(Matrices)


TLCS8-6

如果我们需要表示一个这样的矩阵,可以使用列表的嵌套:

>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

对矩阵中的一行,或者一行中的某一个元素进行访问时如下:

>>> matrix[1]
[4, 5, 6]

>>> matrix[1][1]
5

8.16 字符串和列表

String模块最实用的两个函数涉及到字符串的列表。

split函数可以将一个字符串拆分成一个由词组成的列表,默认情况下,每一个空格被认为是一个词之间的分隔符。

>>> import string
>>> song = “The rain in Spain…”
>>> string.split(song)
[’The’, ’rain’, ’in’, ’Spain...’]

定界符(delimiter)被用来作为一个可选的实际参数,用以指定被用来当做分隔符

>>> string.split(song, ’ai’)
[’The r’, ’n in Sp’, ’n...’]

join函数的作用与split相反,可以将一个列表中的字符串合并起来并且在默认情况下,元素之间会添加一个空格。

>>> list = [’The’, ’rain’, ’in’, ’Spain...’]
>>> string.join(list)
’The rain in Spain…’

join函数也可以使用定界符

>>> string.join(list, ’_’)
’The_rain_in_Spain…’

第九章 元组(Tuples)

9.1 易变性和元组 (Mutability and Tuples)

字符串是不可变的,列表是可变的。

Python中存在一种叫元组的类型,元组与列表很像但是是不可变的。

元组的语法如下,用逗号隔开一个个的元素(括号可有可无):

>>> tuple = ’a’, ’b’, ’c’, ’d’, ’e’

>>> tuple = (’a’, ’b’, ’c’, ’d’, ’e’)

创建单个元素的元组的时候,在元素后面加逗号,如若不然Python将视其为字符串:

>>> t1 = (’a’,)
>>> type(t1)
<type ’tuple’>

>>> t2 = (’a’)
>>> type(t2)
<type ’str’>

对于元组的运算符与列表基本一致:

>>> tuple = (’a’, ’b’, ’c’, ’d’, ’e’)

>>> tuple[0]
’a’

>>> tuple[1:3]

(’b’, ’c’)

我们不能像对列表一样,对元组内的元素进行修改,但是我们可以用新元组来替换它。

>>> tuple = (’a’, ’b’, ’c’, ’d’, ’e’)

>>> tuple[0] = ’A’
TypeError: object doesn’t support item assignment

>>> tuple = (’A’,) + tuple[1:]
>>> tuple
(’A’, ’b’, ’c’, ’d’, ’e’)

9.2 元组的赋值(Tuple assignment)

Python提供的一种元组赋值方式,可以简化变量值的交换:

>>> a, b = b, a

>>> a, b, c, d = 1, 2, 3, 4

赋值号左边的变量个数要和右边的值的个数相等,赋值号右边的每一个值分别赋值给左边的每一个变量。

9.3 作为返回值的元组(Tuples as return values)

Python可以使用元组作为返回值的类型,我们定义一个交换值的函数:

1
2
def swap(x, y):
  return y, x

调用函数swap时,我们函数的返回值赋给两个元素的一个元组:

a, b = swap(a, b)

当我们在封装swap的时候可能很容易会犯错误将其编码成下面这样:

1
2
def swap(x, y): # incorrect version
  x, y = y, x

这样的函数虽然不会报错,但是将不会改变_main里的元组(a, b),它只交换了swap函数内形式参数x与y的值。

9.4 随机数

大多数的计算机程序,每次运行的时候做相同的事情,叫做有其确定性(deterministic)

如果要让程序运行的结果不确定,可以使用随机数。

Python自带有一个生成伪随机数(pseudorandom number)的函数。

random模块含有的random函数将返回一个0.0-1.0之间的任意浮点数。

1
2
3
4
5
import random
for i in range(10):
  x = random.random()
  print x

9.5 随机数的列表 (List of random numbers)

randomList函数可以使用一个整数做实际参数,返回一个由制定个数个随机数字组成的列表:

1
2
3
4
5
def randomList(n):
  s = [0] * n
  for i in range(n):
    s[i] = random.random()
  return s

>>> randomList(8)
0.15156642489
0.498048560109
0.810894847068
0.360371157682
0.275119183077
0.328578797631
0.759199803101
0.800367163582

如果我们要产生0.0值一个最大值high之间的随机数,可以使用x乘以high。

使用random函数生成的数字出现的几率应该是相等的。下面我们将通过编程测试,将数字分到指定长度的一段段中(一个个桶),计数每一段(每个桶)中的数字个数。

9.6 计数 (Counting)

一个好的尝试是将这样的问题分解,然后寻找以前见过的计算模式。

这里,我们将遍历一个数字的列表,然后计数每一段里数字的个数。

7.8中我们编码一个计数的程序如下:

1
2
3
4
5
count = 0
for char in fruit:
  if char == ’a’:
    count = count + 1
print count

我们对其进行修改:

1
2
3
4
5
count = 0
for num in t:
  if low < num < high:
    count = count + 1
print count

最后一步我们将其封装进函数里:

1
2
3
4
5
6
def inBucket(t, low, high):
  count = 0
  for num in t:
    if low < num < high:
      count = count + 1
  return count

这种程序开发模式叫做模式匹配(Pattern Matching)

9.7 许多桶 (Many buckets)

当桶的数目为4的时候:

bucket1 = inBucket(a, 0.0, 0.25)
bucket2 = inBucket(a, 0.25, 0.5)
bucket3 = inBucket(a, 0.5, 0.75)
bucket4 = inBucket(a, 0.75, 1.0)

存在的问题有两个:

1是如果要增加桶的数量,我们需要增加变量的数量
2我们需要自己计算桶的区间

先解决第二个问题,如果令桶的总数为numBuckets,那么每个桶的区间为1.0/numBuckets。

我们使用循环来计算每个桶的区间:

1
2
3
4
5
bucketWidth = 1.0 / numBuckets
  for i in range(numBuckets):
    low = i * bucketWidth
    high = low + bucketWidth
    print low, "to", high

当numBuckets为8时,上面程序将输出:

0.0 to 0.125
0.125 to 0.25
0.25 to 0.375
0.375 to 0.5
0.5 to 0.625
0.625 to 0.75
0.75 to 0.875
0.875 to 1.0

回到第一个问题,我们需要一个能储存8个整数,并能够用循环变量一次次指向其中每一个整数的方法。

可以用列表来实现:

1
2
3
4
5
6
7
8
numBuckets = 8
buckets = [0] * numBuckets
bucketWidth = 1.0 / numBuckets
for i in range(numBuckets):
  low = i * bucketWidth
  high = low + bucketWidth
  buckets[i] = inBucket(t, low, high)
print buckets

当t取1000时,程序结果为类似下面:

[138, 124, 128, 118, 130, 117, 114, 131]

9.8 一种单程的解决方案(A single-pass solution)

上面的解决方案虽然可以正确工作,但是并不是十分有效率。

程序每一次调用inBucket函数,函数将遍历整个列表,当桶的数量增加的时候,遍历的次数也将增加。

如果能够只遍历一次列表,按照每个值计算出该值应该进入的桶的索引,那么我们就能够优化程序。

在前面的部分我们使用一个索引i,将其乘以bucketWidth来确定桶的左区间。现在我们需要对一个在0.0-1.0之间的值进行判断,确定它属于哪一个桶。这是前面一个问题的相反。

1
2
3
4
5
numBuckets = 8
buckets = [0] * numBuckets
for i in t:
  index = int(i * numBuckets)
  buckets[index] = buckets[index] + 1

这里我们使用了int函数来将一个浮点数转化为一个整数。

像buckets这样的含有不同区间内值的数量的列表,又叫做直方图(histogram)。

 

How to Think Like a Computer Scientist

How to Think Like a Computer Scientist
Learning with Python

by Allen Downey, Jeff Elkner and Chris Meyers.

PDF Version : Link

Leave a Reply

Your email address will not be published. Required fields are marked *