Python教程之四----- 数据结构

这一章将更深入的描述一些你已经学过的东西,并且同样添加一些新的东西

5.1 更多关于列表

列表数据类型有更多的方法,下面是列表对象的所有方法:

list.append(x)

     将一个项添加到列表的末尾。等价于a[len(a):]=[x]。

list.extend(iterable)

     用迭代器中的所有项扩展列表。等价于a[len(a):] = iterable

list.insert(i,x)

     在给定位置插入一个项。第一个参数表示被待插元素插在前面的元素的索引,所以a.insert(0,x)将在列表的最开始插        入,a.insert(len(a),x)等价于a.append(x)

list.remove(x)

     移除列表中值为x的第一个项。如果没有这个项将会抛错。

list.pop([i])

     移除列表中制定位置的项,并且返回。如果没有指定索引,a.pop()移除并返回列表的最后一个项。(方法签名中I      周围的方括号表示这个参数是可选的,而不是你应该在那个位置输入方括号,后面也将保持这种方式。)

list.clear()

     移除列表的所有项。等价于del a[:]

list.index(x[,start[,end]])

     返回列表中值为x的从零开始的第一个项的索引值。如果没有这个项,将抛出ValueError错误

       可选的参数start和end如切片符号里面的一样用于将搜索限制在列表的一个特殊子序列。返回的索引是相对于完整      序列的开始而不是start参数的开始位置来计算的

list.count(x)

     返回列表中x出现的次数

list.sort(key=None,reverse=False)

     对列表中的项适当的排序(参数用于定制化排序)

list.reverse()

     适当的将列表中的元素颠倒

list.copy()

     返回一个列表的浅拷贝。等价于a[:].

使用大部分方法的例子:

>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4)  # Find next banana starting a position 4
6
>>> fruits.reverse()
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape')
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort()
>>> fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
>>> fruits.pop()
'pear'

你也许注意到了,像insert,remove,sort这样的方法仅仅只是修改了列表却没有返回值打印出来--它们返回默认的值None。在Python中对于所有可变的数据结构来说这是一个设计原则。


5.1.1  将列表用作堆栈

列表的方法很容易就可以实现将列表当作堆栈来使用,即最后的元素添加进来当作第一个元素取出(后进先出)。在堆栈的顶部添加元素,可以使用append()。在堆栈的顶部取一个项可以使用pop()方法而不需要明确的索引。例如:

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]

5.1.2 将列表用作队列

将列表用作队列也是可以的,即添加的第一个元素也被第一个取出(先进先出);然后,列表处理这个并不效率。当从列表的末尾append和pop是和块的,而在列表的开头insert和pop是非常慢的(因为所有其他的元素都要移动一次)。


要实现一个队列,使用collections.deque,这个实现了从两端快速进行append和pop。例如:

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.popleft()                 # The first to arrive now leaves
'Eric'
>>> queue.popleft()                 # The second to arrive now leaves
'John'
>>> queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

5.1.3  列表推导

列表推导提供一个简洁的方式来创建列表。常见的应用程序创建新的列表,这些列表中每个元素是应用于另一个序列或者迭代器的每一个成员操作的结果,或是创建满足一个确定条件的这些元素的子序列。


例如,假设我们想创建一个平方的列表:

>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

请注意这创建(或重写)一个名为x的变量,这个变量在循环完成后依然存在。我们可以没有任何副作用的计算出一个平方的列表:

squares = list(map(lambda x: x**2, range(10)))
或者,等价的:
squares = [x**2 for x in range(10)]

这个更简洁和更有可读性。


一个列表推导有包含一个紧跟for子句的方括号组成,然后是0个或者更多的for或者if子句。结果将会是一个有紧跟的for和if子句计算产生的新的列表。例如,如果他们不相等这个列表结合2个列表:

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

并且等价于:

>>> combs = []
>>> for x in [1,2,3]:
...     for y in [3,1,4]:
...         if x != y:
...             combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

请注意,在这2个片段中for语句和if语句的顺序是一样的。

如果表达式是元祖(e.g. 先前例子中的(x,y)),它一定要加上括号。

>>> vec = [-4, -2, 0, 2, 4]
>>> # create a new list with the values doubled
>>> [x*2 for x in vec]
[-8, -4, 0, 4, 8]
>>> # filter the list to exclude negative numbers
>>> [x for x in vec if x >= 0]
[0, 2, 4]
>>> # apply a function to all the elements
>>> [abs(x) for x in vec]
[4, 2, 0, 2, 4]
>>> # call a method on each element
>>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> # create a list of 2-tuples like (number, square)
>>> [(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
>>> # the tuple must be parenthesized, otherwise an error is raised
>>> [x, x**2 for x in range(6)]
  File "<stdin>", line 1, in <module>
    [x, x**2 for x in range(6)]
               ^
SyntaxError: invalid syntax
>>> # flatten a list using a listcomp with two 'for'
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

列表推导能包含复杂的表达式和嵌套的函数:

>>> from math import pi
>>> [str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

5.1.4 嵌套列表推导

列表推导中的初始表达式能是任何表达式,包括另一个列表推导。


考虑下面这个例子,一个3×4的矩阵被实现为一个包含3个长度为4的列表:

>>> matrix = [
...     [1, 2, 3, 4],
...     [5, 6, 7, 8],
...     [9, 10, 11, 12],
... ]

下列列表推导将转换行和列:

>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

正如我们在前一节中看到的一样,嵌套的列表在其后的for中评估,所以这个例子等价于:

>>> transposed = []
>>> for i in range(4):
...     transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
反过来也是一样:
>>> transposed = []
>>> for i in range(4):
...     # the following 3 lines implement the nested listcomp
...     transposed_row = []
...     for row in matrix:
...         transposed_row.append(row[i])
...     transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

在现实世界中,你应该更倾向于内置函数而不是复杂的流语句。zip()函数对于这种情况将会做的更好:

list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

5.2 Del语句

有一种方式可以通过给定的索引而不是值来从一个列表中移除一个项:del语句。这和能返回一个值的pop()方法不同。del语句同样也能从一个列表中移除切片或者清楚整个列表(如我们之前将一个空的列表赋值给一个切片一样)。例如:

>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]
>>> del a[:]
>>> a
[]

del同样也能用于删除整个变量:

>>> del a
引用后面的名称a是一个错误(至少直到另外一个值被赋给它)。我们在稍后将讨论更多del的用法。


5.3 元组和序列

我们知道列表和字符串有许多共同的特性,例如索引和切片操作。他们是序列数据类型的2个例子。因为Python是一个不断发展的语言,其他序列数据类型也可能被添加。同样有另外一个标准序列数据类型:元组。

一个元组包含一个由逗号分隔的数字的值,例如:
>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
>>> # Tuples are immutable:
... t[0] = 88888
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> # but they can contain mutable objects:
... v = ([1, 2, 3], [3, 2, 1])
>>> v
([1, 2, 3], [3, 2, 1])
正如你看到的,输出的元组总是被圆括号包括,所以嵌套元组能被正确的解释;输入可能有或者没有圆括号,尽管圆括号是需要的(如果元组是一个大的表达式的一部分)。给一个元组的独立项赋值是不幸的,但是可以创建包含可变对象的元组,例如列表。

尽管元组和列表看起来很相似,他们通常因不同的目的而用在不同的情况下。元组是不可变的,并且通常包含一个多样化的元素序列,可以通过拆分来获得。列表是可变的,并且他们的元素通常是相似的,可以通过迭代器循环列表来获得。

一个特殊的问题是包含0和1项的元组的构造:语法有一些额外的奇怪方式来适应这些。空元组是由一对空的圆括号组成;拥有一个项的元组是由一个值和一个逗号(只在圆括号中包含一个值是不够的)。丑,但是高效,例如:
>>> empty = ()
>>> singleton = 'hello',    # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)
语句t= 12345,54321,'hello!'就是一个元组打包的例子:值12345,54321和'hello!'被打包在一个元组中。相反的操作是:
>>> x, y, z = t
这适当的被称为序列拆分并且右值的任何序列都能生效。序列拆分需要在左边有和右边序列中元素数量相同的变量。请注意,多重赋值就是元组打包和序列拆分的一个组合。

5.4 集合

Python同样包括集合数据类型。集合是一个没有重复元素的聚集。基础用法包括成员测试和消除重复条目。集合同样支持数学上的操作例如,并集,交集,差分和对称差分。


花括号或者set()函数可以用来创建一个集合。注意:为了创建一个空的集合你必须用set(),而不是{};后者用于创建一个空的字典,我们将在下节讨论这种新的数据类型。


下面是一个简洁的示例:

>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket)                      # show that duplicates have been removed
{'orange', 'banana', 'pear', 'apple'}
>>> 'orange' in basket                 # fast membership testing
True
>>> 'crabgrass' in basket
False

>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a                                  # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
>>> a - b                              # letters in a but not in b
{'r', 'd', 'b'}
>>> a | b                              # letters in a or b or both
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b                              # letters in both a and b
{'a', 'c'}
>>> a ^ b                              # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}

类似于列表推导式,集合推导式也同样支持:

>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}


5.5 字典

另一个有用的Python内置数据类型是字典。字典有时在其他语言里作为“关联记忆”或者“关联数组”。不像由数字形成索引的序列,字典的索引是keys,是一个不可变类型;字符串和数字总可以是keys。如果只包含字符串,数字或者元组,那么元组也能成为keys;如果元组直接或者间接包含任何可变的对象,它都不能用作key。列表不能用作key,因为列表能被索引赋值,切片赋值,或者像append()和extend()方法修改。


最好将字典想象成是一个无序的key:value对,并且要求key是唯一的(在一个字典内)。一对花括号创建一个空的字典:{}。将一个用逗号分隔的key:value对列表放在花括号内将在字典中添加初始的键值对;这也是字典在输出上被写的方式。


字典的主要操作是用某些key来存储一个值并通过给定的key来提取值。可以用del来删除一个键值对。如果你使用一个已经在使用的key,那么旧的与这个key相关联的值将会被遗忘。用一个不存在的key来提取一个值将会产生一个错误。


在字典上使用list(d.keys()) 将返回在字典中使用的所有列的一个列表,无序的(如果你要排序,就用sorted(d.keys()) 代替)。检查是否一个单独的key在字典中,使用in关键字。


这有一个使用字典的例子:

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
>>> list(tel.keys())
['irv', 'guido', 'jack']
>>> sorted(tel.keys())
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False

dict()构造函数将直接从键值对序列中生成字典:

>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}

额外的,字典推导式也能用于从一个任意的键值表达式中创建字典:

>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}

当keys是简单的字符串是,有时候可以很容易的使用关键字参数来指定:

>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}

5.6 循环技巧

当遍历数组时,key和对应的value能使用items()方法在同一时间提取。

>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
...     print(k, v)
...
gallahad the pure
robin the brave

当遍历序列时,索引的值和对应的值能用enumerate()方法来同时提取

>>> for i, v in enumerate(['tic', 'tac', 'toe']):
...     print(i, v)
...
0 tic
1 tac
2 toe
为了在同时循环2个或更多序列,可以用zip()函数来配对条目
>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
...     print('What is your {0}?  It is {1}.'.format(q, a))
...
What is your name?  It is lancelot.
What is your quest?  It is the holy grail.
What is your favorite color?  It is blue.

为了反向遍历序列,首先在一个前面的方向上指定序列并且调用reversed()函数。

>>> for i in reversed(range(1, 10, 2)):
...     print(i)
...
9
7
5
3
1

为了用排序顺序遍历一个序列,使用sorted()方法,它返回一个新的排序的列表,并保证源列表不变。

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
...     print(f)
...
apple
banana
orange
pear
当你在循环一个列表的时候去改变它是很有诱惑力的;然而,通常简单而安全的方法是创建一个新的列表来代替。
>>> import math
>>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
>>> filtered_data = []
>>> for value in raw_data:
...     if not math.isnan(value):
...         filtered_data.append(value)
...
>>> filtered_data
[56.2, 51.7, 55.3, 52.5, 47.8]

5.7 更多关于条件

使用在while和if语句中的条件能包含任何操作,而不仅仅是比较。


比较操作符in和not in 检查是否一个值在列表中是否存在(或不存在)。

操作符is和is not比较是否2个对象是真正的同一个对象;这通常对列表那样的可变对象才重要。所有的比较操作符有同样的优先权,同时也比所有的数值运算符低。


比较可以组合,例如:a < b == c 表示是否a 小于b 并且 b 等于 c。


比较也能用布尔操作符and和or来组合,并且一个比较(或者任何其他布尔表达式)的结果也可能为not。它的优先级比比较操作符要低;在他们中,not拥有最高的优先级,而or最低,所以A and not B or C等价于(A and (not B)) or C。同样,圆括号也可以用于表达需要的组合。


布尔操作符and和or是被称作 short-circuit(短路)运算符:他们的参数从左到右运算,并且一旦结果确定了,运算就会停止。例如,if A and C 是真的但是B是False, A and B and C将不会运算表达式C。当作为一个通用值而不是一个布尔值是,短路操作符的返回值是最后运算的参数。


可以将比较或者其他布尔表达式的值赋值给一个变量,例如:

>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'

注意在Python中,不像C,赋值不能在表达式中出现。C程序员可能会抱怨,但是它避免了在C程序中经常碰到的问题:当想要输入==是却成了=。


5.8 比较序列和其他类型

序列对象可以和其他相同序列类型的对象比较。比较实用词典序列排序:首先是前2个项开始比较,如果他们不同就决定了比较的结果;如果它们相等,就比较下2个项,等等,直到每个序列都被耗尽。如果要比较的2个项是同一个类型的序列,那么词汇比较是递归进行的。如果2个序列的所有项都相等,序列就被认为是相等的。如果一个序列是另一个序列的子序列,较短的序列是相对较小(较少)的那个。字符串的词汇排序使用Unicode代码点数字来排序当个字符。下面是相同类型序列的比较的例子:

(1, 2, 3)              < (1, 2, 4)
[1, 2, 3]              < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4)           < (1, 2, 4)
(1, 2)                 < (1, 2, -1)
(1, 2, 3)             == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)
123

注意,如果对象有适当的比较方法,实用<或者>来比较不同类型的对象是合法的。例如,混合的数字类型根据他们的数值来比较,所以0等于0.0,等等。否则,解释器会抛出类型错误一场而不是提供一个任意排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值