数据结构(python)


主要讲述python语言内建功能, 常见的数据结构主要: 元组, 列表, 字典和集合.

1. 元组

元组是固定长度, 不可变的python对象序列. 创建元组很简单的方法就是用逗号隔开:

In [1]: a = 4, 5, 6
In [2]: a
Out[2]: (4, 5, 6)

当需要表达更复杂的元组时,需要加上括号:

In [3]: nest = (4, 5, 6), (7, 8)
In [4]: nest
Out[4]: ((4, 5, 6), (7, 8))

可以使用tuple函数将任意序列或迭代器转换为元组:

In [5]: tuple([4, 0, 2])
Out[5]: (4, 0, 2)

In [6]: a = tuple('string')
In [7]: a
Out[7]: ('s', 't', 'r', 'i', 'n', 'g')

元组的元素可以通过[]来获取,在大多数序列类型中都可以使用这个方法:

In [8]: a[0]
Out[8]: 's'

虽然对象元组中存储的对象其自身是可以改变的, 但是元组一旦创建, 各个位置上的对象是无法被修改的:

In [9]: a = tuple(['foo', [1, 2], True])
In [10]: a[2] = False

上述会报TypeError的错误. 但是如果一个元组的对象是可变的, 例如列表, 那么就可以在列表内部进行修改:

In [11]: a[1].append(3)
In [12]: a
Out[12]: ('foo', [1, 2,3], True)

可以使用+来生成更长的元组, 也可以使用* 号来复制出更多同样的元组.

1.1 元组拆包

如果想要将元组型的表达式复制给变量, python会对等号右边的值进行拆包:

In [15]: a = (4, 5, 6)
In [16]: x, y, z = a

In [17]: y
Out[17]: 5

嵌套拆包也是可以的:

In [18]: a = 4, 5, (6, 7)

In [19]: a
Out[19]: (4, 5, (6, 7))

使用这个功能可以轻松交换便令名. 但是再python中, 交换两个变量可以使用下面的命令完成:

In [21]: a, b = 1,2

In [22]: a
Out[22]: 1

In [23]: b
Out[23]: 2

In [24]: b, a = a, b

In [25]: a
Out[25]: 2

In [26]: b
Out[26]: 1

python有一个高级拆包功能, 用于从元组开始的位置采集一些元素. 这个功能使用特殊的语法*rest, 用于再函数调用时获取任意长度的位置参数列表:

In [29]: values = 1, 2, 3, 4, 5

In [30]: a, b, *rest = values

In [31]: a, b
Out[31]: (1, 2)

In [32]: rest
Out[32]: [3, 4, 5]

rest部分有时是向丢弃的数据, rest这个变量并没有什么特殊之处, 为了方便, 很多变成会使用下划线代替变量

In [33]: a, b, *_ = values

1.2 元组方法

由于元组的内容和长度时无法改变的, 它的实例方法很少. 一个常见的有用方法时count(列表中也可以用), 用于计量某个数值在元组中出现的次数:

In [34]: a = (1, 2, 2, 2, 3, 4, 2)

In [35]: a.count(2)
Out[35]: 4

2 列表

与元组不同, 列表的长度时可变的, 它所包含的内容也是可以修改的. 可以使用[ ]或者list类型函数来定义列表:

In [36]: a_list = [2, 3, 7, None]

In [37]: a = ('foo', 'bar', 'baz')

In [38]: b_list = list(a)

In [39]: b_list
Out[39]: ['foo', 'bar', 'baz']

In [40]: b_list[1] = 'peekaboo'

In [41]: b_list
Out[41]: ['foo', 'peekaboo', 'baz']

列表与元组相似, 他们很多用法也是相似的, 但是元组不能更改.
list函数在数据处理中常用于将迭代器或者生成器转化为列表:

In [42]: gen = range(10)

In [43]: gen
Out[43]: range(0, 10)

In [44]: list(gen)
Out[44]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

2.1 增加和移除元素

使用append方法可以将元素添加到列表的尾部:

In [45]: b_list.append('dwarf')

In [46]: b_list
Out[46]: ['foo', 'peekaboo', 'baz', 'dwarf']

使用insert方法可以将元素插入到指定的列表位置:

In [47]: b_list.insert(1, 'red')

In [48]: b_list
Out[48]: ['foo', 'red', 'peekaboo', 'baz', 'dwarf']

insert 和 append相比, 计算代价更高. 因为子序列元素不得不在内部移动为新元素提供空间. 如果想要在序列的头部和尾部插入元素, 那么应该使用collections.deque, 它是一个双端队列, 可以满足头尾部都增加的要求.
insert的反向操作时pop, 该操作会将特定位置的元素移除并返回:

In [48]: b_list
Out[48]: ['foo', 'red', 'peekaboo', 'baz', 'dwarf']

In [49]: b_list.pop(2)
Out[49]: 'peekaboo'

In [50]: b_list
Out[50]: ['foo', 'red', 'baz', 'dwarf']

元素可以通过remove方法移除, 该方法会定位一个符合要求的值移除它:

In [51]: b_list.append('foo')

In [52]: b_list
Out[52]: ['foo', 'red', 'baz', 'dwarf', 'foo']

In [53]: b_list.remove('foo')

In [54]: b_list
Out[54]: ['red', 'baz', 'dwarf', 'foo']

如果不考虑性能, 通过使用append和remove, 可以将python的列表用作一种完全合适的多集和结构.
使用in关键字可以检索一个值是否在列表中:

In [55]: 'dwarf' in b_list
Out[55]: True

not 关键字可以用作in的反义词, 表示不在:

In [56]: 'dwarf' not in b_list
Out[56]: False

与字典, 集合相比, 检查列表中是否包含一个值是非常缓慢的. 这是因为python在列表中进行了线性逐个扫描, 而在字典和集合中python时同时检查所有元素的.

2.2 连接和联合列表

与元素类似, 两个列表可以使用+号来连接:

In [57]: [4, None, 'foo'] + [7, 8, (2, 3)]
Out[57]: [4, None, 'foo', 7, 8, (2, 3)]

如果有一个已经定义的列表, 可以使用extend方法向该列表添加多个元素:

In [58]: x = [4, None, 'foo']

In [59]: x.extend([7, 8, (2, 3)])

In [60]: x
Out[60]: [4, None, 'foo', 7, 8, (2, 3)]

请注意通过添加内容来连接列表是一种相对高代价的操作, 这是因为连接过程中创建了新列表,并且还要复制对象. 使用extend将元素添加到已经存在的列表时更好的方式, 尤其时在需要构建一个大型列表时:

In [61]: everything = []

In [62]: for chunk in list_of_lists:
    ...:     everything.extend(chunk)

并且上述实现要比下面更快:

In [61]: everything = []

In [62]: for chunk in list_of_lists:
    ...:     everything = everything + chunk

2.3 排序

可以调用列表的sort方法对列表进行内部排序(不需要新建对象):

In [63]: a = [7, 2, 5, 1, 3]

In [64]: a.sort()

In [65]: a
Out[65]: [1, 2, 3, 5, 7]

sort有一些选项偶尔会派上用场. 其中一项是传递一个二级排序key(一个生成排序值的函数). 例如, 通过字符串的长度排序:

In [66]: b = ['saw', 'small', 'He', 'foxes', 'six']

In [67]: b.sort(key=len)

In [68]: b
Out[68]: ['He', 'saw', 'six', 'small', 'foxes']

2.4 二分搜索和已排序列表的维护

内建的bisect模块实现了二分搜索和已排序列表的插值. bisect.bisect会找到元素应当被插入的位置, 并保持序列排序, 而bisect.insort将元素插入到相应位置:

In [69]: import bisect

In [70]: c = [1, 2, 2, 2, 3, 4, 7]

In [71]: bisect.bisect(c, 2)
Out[71]: 4

In [72]: bisect.bisect(c,5)
Out[72]: 6

In [73]: bisect.insort(c, 6)

In [74]: c
Out[74]: [1, 2, 2, 2, 3, 4, 6, 7]

2.5 切片

使用切片符号可以对大多数序列类型选取其子集, 它的基本形式是将start:stop传入到索引符号[ ]中:

In [75]: seq = [7, 2, 3, 7, 5, 6, 0, 1]

In [76]: seq[1:5]
Out[76]: [2, 3, 7, 5]

切片还可以将序列赋值给变量:

In [77]: seq[3:4] = [6, 3]

In [78]: seq
Out[78]: [7, 2, 3, 6, 3, 5, 6, 0, 1]

由于起始位置start的索引是包含的, 而结束位置stop的索引不包含, 因此元素的数量是stop - start.
start和stop是可以省略的, 如果省略的话会默认传入序列的起始位置或结束位置:

In [79]: seq[:5]
Out[79]: [7, 2, 3, 6, 3]

In [80]: seq[3:]
Out[80]: [6, 3, 5, 6, 0, 1]

负索引可以从序列的尾部进行索引:

In [81]: seq[-4:]
Out[81]: [5, 6, 0, 1]

In [82]: seq[-6:-2]
Out[82]: [6, 3, 5, 6]

步进值setp可以在第二个冒号后使用, 意思是每隔多少进行取值:

In [83]: seq[::2]
Out[83]: [7, 3, 3, 6, 1]

当需要对列表或元组进行翻转时, 一种很聪明的用法就是向步进取值为-1:

In [84]: seq[::-1]
Out[84]: [1, 0, 6, 5, 3, 6, 3, 2, 7]

3. 内建序列函数

3.1 enumerate

在遍历一个序列的同时追踪当前元素的索引. 一个自行实现的方法:

i = 0
for value in collection:
	# 使用值做点事儿
	i += 1

由于这种场景很常见, 所以python内建了enumerate函数, 返回(i, value)元组的序列, 其中value是元素的值, i 是元素的索引:

for i, value in enumerate(collection):
	# 使用值做点事儿

当需要对数据建立索引时, 一种有效的模式就是使用enumerate构造一个字典, 将序列值映射到所因为值上:

In [85]: some_list = ['foo', 'bar', 'baz']

In [86]: mapping = {}

In [87]: for i, v in enumerate(some_list):
    ...:     mapping[v] = i
    
In [89]: mapping
Out[89]: {'foo': 0, 'bar': 1, 'baz': 2}

3.2 sorted

sorted函数返回一个根据任意序列中的元素新建的已排序列表:

In [90]: sorted([7, 1, 2, 6, 0, 3, 2])
Out[90]: [0, 1, 2, 2, 3, 6, 7]

sorted函数接受的参数与列表的sort方法一致.

3.3 zip

zip将列表, 元组或其它序列的元素配对, 新建一个元组构成的列表:

In [91]: seq1 = ['foo', 'bar', 'baz']

In [92]: seq2 = ['one' , 'two', 'three']

In [93]: zipped = zip(seq1, seq2)

In [94]: list(zipped)
Out[94]: [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]

zip可以处理任意长度的序列, 它生成列表长度由最短的序列决定:

In [95]: seq3 = [False, True]

In [96]: list(zip(seq1, seq2, seq3))
Out[96]: [('foo', 'one', False), ('bar', 'two', True)]

zip的常用场景为同时遍历多个序列, 有时候会和enumerate同时使用:

In [97]: for i, (a, b) in enumerate(zip(seq1, seq2)):
    ...:     print('{0}: {1}, {2}'.format(i, a, b))
    ...: 
0: foo, one
1: bar, two
2: baz, three

给定一个已经配对的序列时, zip函数有一种机智的方式区拆分序列. 这种方法的另一种思路就是将行的列表转化为列的列表. 语法上看上去很魔幻:

In [98]: pitcters = [('Nolan', 'Ryan'), ('Roger', 'Clemens'), ('Schilling', 'Cur
    ...: t')]
    In [100]: first_names, last_names = zip(*pitcters)

In [101]: first_names
Out[101]: ('Nolan', 'Roger', 'Schilling')

In [102]: last_names
Out[102]: ('Ryan', 'Clemens', 'Curt')

3.4 reversed

reversed函数将序列的元素倒序排列:

In [103]: list(reversed(range(10)))
Out[103]: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

未完待续。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空下0516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值