python数据结构和序列

1.元祖

元祖是一种固定长度、不可变的Python对象序列。创建元祖最简单的方法就是用逗号分隔序列值。

tup = 4,5,6
print(tup)  #(4,5,6)

用更复杂的表达式来定义元祖时,一般需要用括号将值包起来。

complex_tup = (1,2,3),(4,5)
print(complex_tup) #((1, 2, 3), (4, 5))

我们也可以使用tuple函数将任意序列或者迭代器转换为元祖:

tup_1 = tuple([4,0,2])
print(tup_1) #(4, 0, 2)
tup_2 = tuple('string')
print(tup_2) #('s', 't', 'r', 'i', 'n', 'g')

元祖的元素可以通过[ ]来获取,python中的序列索引是从0开始的。
虽然元祖中存储的对象其自身是可变的,但是元祖一旦创建,各个位置上的对象是无法被修改的:

tup = tuple(['foo',[1,2],True])
tup[2] = False
print(tup)
---------------------------------------------------------------------------
Traceback (most recent call last):
  File "C:/Users/asus/PycharmProjects/数据分析/3.1数据结构和序列.py", line 16, in <module>
    tup[2] = False
TypeError: 'tuple' object does not support item assignment

如果元祖中的一个对象是可变的,例如列表,你可以在它内部进行修改:

tup = tuple(['foo',[1,2],True])
tup[1].append(4)
print(tup) #('foo', [1, 2, 4], True)

我们可以使用+号连接元祖来生成更长的元祖:

a = (4,5,6)+(None,4,[8,9])+('xiewangting',)
print(a) #(4, 5, 6, None, 4, [8, 9], 'xiewangting')

将元祖乘以整数,则会和列表一样,生成含有多份拷贝的元祖:

print(('foo','bar') * 4) #('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')

1.1元祖拆包

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

tup = (4,5,6)
a,b,c = tup
print(a) #4
print(b) #5
print(c) #6

即使是嵌套的元祖也可以拆包:

tup = 4,5,(6,7)
a,b,(c,d) = tup
print(c) #6

在python中交换两数的值,可以使用如下代码完成:

a,b = 1,2
a,b = b,a
print(a) #2
print(b) #1

拆包的一个常用场景就是遍历元祖或列表组成的序列:

扫描二维码关注公众号,回复: 4264094 查看本文章
seq = [(1,2,3),(4,5,6),(7,8,9)]
for a,b,c in seq:
    print('a={0},b={1},c={2}'.format(a,b,c)
---------------------------------------------------------------------------
a=1,b=2,c=3
a=4,b=5,c=6
a=7,b=8,c=9

python语言新增了一些更为高级的元祖拆包功能,我们可以用元祖的起始位置“采集”一些元素,这个功能使用特殊的语法*rest,用于在函数调用时获取任意长度的位置参数列表:

values = 1,2,3,4,5
a,b,*rest = values
print(a,b) #1 2
print(rest) #[3, 4, 5]

rest部分有时是你想要丢弃的数据,rest这个变量名并没有特殊之处,出于习惯,很多python程序员会用下划线来表示想要丢弃的数据。

1.2元祖方法

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

a = (1,2,2,2,3,4,5,6,6,7,7,5,8)
print(a.count(2)) #3

2列表

与元祖不同,列表的长度是可以变化的,它所包含的内容也是可以修改的。我们可以使用中括号[ ]或者list类型函数来定义列表:

a_list = [2,3,5,6,None]
tup = ('abc','xwt','www')
b_list = list(tup)
print(b_list) #['abc', 'xwt', 'www']
b_list[1] = 'aaa'
print(b_list) #['abc', 'aaa', 'www']

list函数在数据处理中常用于将迭代器或者生成器转化为列表:

gen = range(10)
print(gen) #range(0, 10)
print(list(gen)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

2.1增加和移除元素

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

b_list = ['aaa','bbb','ccc']
b_list.append('dwarf')
print(b_list) #['aaa', 'bbb', 'ccc', 'dwarf']

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

b_list = ['aaa','bbb','ccc']
b_list.insert(1,'ggg')
print(b_list) #['aaa', 'ggg', 'bbb', 'ccc']

插入位置的范围在0到列表长度之间。
insert与append相比,计算代价更高。因为子序列元素不得不在内部移动为新元素提供空间。
insert的反操作是pop,该操作会将特定位置的元素移除并返回:

b_list = ['aaa','bbb','ccc']
print(b_list.pop(2)) #ccc
print(b_list) #['aaa', 'bbb']

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

b_list = ['aaa','bbb','ccc']
b_list.append('aaa')
b_list.remove('aaa')
print(b_list) #['bbb', 'ccc', 'aaa']

使用in关键字可以检查一个值是否在列表中:

b_list = ['aaa','bbb','ccc']
print('aaa' in b_list) #True

not关键字可以用作in的反义词,表示“不在”。
与字典、集合相比,检查列表中是否包含一个值是非常缓慢的。这是因为python在列表中进行了线性逐个扫描,而在字典和集合中python是同时检查所有元素的(基于哈希表)。

2.2连接和联合列表

与元祖类似,两个列表也可以使用+号连接。
如果有一个已经定义的列表,那么我们可以用extend方法向该列表添加多个元素:

a = [3,None,'bar']
a.extend([8,9,(6,7)])
print(a) #[3, None, 'bar', 8, 9, (6, 7)]

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

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

上述实现比下述实现更快:

everything = []
for chunk in list_of_lists:
    everything = everything + chunk

2.3排序

我们可以调用列表的sort方法来对列表进行内部排序(无须新建一个对象):

a = [7,5,4,6,4,3,2,1]
a.sort()
print(a) #[1, 2, 3, 4, 4, 5, 6, 7]

sort函数中有一个项是传递一个二级排序key----一个用于生成排序值的函数。例如,我们可以通过字符串的长度进行排序:

b = ['xiewt','xwt','xiewangting','xiewangt']
b.sort(key = len)
print(b) #['xwt', 'xiewt', 'xiewangt', 'xiewangting']

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

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

import bisect
c = [1,2,2,2,3,4,7]
print(bisect.bisect(c,2)) #4
print(bisect.bisect(c,5)) #6
bisect.insort(c,6)
print(c) #[1, 2, 2, 2, 3, 4, 6, 7]

2.5切片

使用切片符号可以对大多数序列类型选取其子集,它的基本形式是将start:stop传入到索引符号[ ]中。
由于起始位置start的索引是包含的,而结束位置stop的索引并不包含,因此元素的数量是stop-start。
start和stop是可以省略的,如果省略的话会默认传入序列的起始位置或结束位置。
负索引可以从序列的尾部进行索引:

seq = [7,5,0,4,3,6,7,3,1]
print(seq[-4:]) #[6, 7, 3, 1]
print(seq[-7:-4]) #[0, 4, 3]

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

seq = [7,5,0,4,3,6,7,3,1]
print(seq[::2]) #[7, 0, 3, 7, 1]

当需要对列表或元祖进行翻转时,我们可以向步进传值-1:

seq = [7,5,0,4,3,6,7,3,1]
print(seq[::-1]) #[1, 3, 7, 6, 3, 4, 0, 5, 7]

3内建序列函数

3.1enumerate

我们经常需要在遍历一个序列的同时追踪当前元素的索引。可以使用python内建函数enumerate来实现,返回了(i,value)元祖的序列,其中value是元素的值,i是元素的索引:

for i,value in enumerate(collection):

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

some_list = ['foo','bar','baz']
mapping = {}
for i,v in enumerate(some_list):
    mapping[v] = i
print(mapping) #{'foo': 0, 'bar': 1, 'baz': 2}

3.2sorted

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

print(sorted([7,8,5,4,6,3,4,6,7])) #[3, 4, 4, 5, 6, 6, 7, 7, 8]
print(sorted('horse race')) [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']

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

3.3zip

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

seq1 = ['foo','bar','baz']
seq2 = ['one','two','three']
zipped = zip(seq1,seq2)
print(list(zipped)) #[('foo', 'one'), ('bar', 'two'), ('baz', 'three')]

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

seq1 = ['foo','bar','baz']
seq2 = ['one','two','three']
seq3 = [False,True]
print(list(zip(seq1,seq2,seq3))) #[('foo', 'one', False), ('bar', 'two', True)]

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

seq1 = ['foo','bar','baz']
seq2 = ['one','two','three']
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函数有一种机智的方式去“拆分”序列:

pitchers = [('Nolan','Ryan'),('Roger','Clemens'),('Schilling','Curt')]
first_names,last_names = zip(*pitchers)
print(first_names) #('Nolan', 'Roger', 'Schilling')
print(last_names) #('Ryan', 'Clemens', 'Curt')

3.4reversed

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

print(list(reversed(range(10)))) #[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

reversed是一个生成器,因此如果没有实例化(例如使用list函数或进行for循环)的时候,它并不会产生一个倒序的列表。

4字典

字典(dict)可能是python内建数据结构中最重要的。它更为常用的名字是哈希表或者是关联数组。字典拥有键值对集合,其中键和值都是python对象。字典表示方法如下:

empty_dict = {}
d1 = {'a':'some value','b':[1,2,3,4]}
print(d1)  #{'a': 'some value', 'b': [1, 2, 3, 4]}

我们也可以访问、插入或设置字典中的元素,就和访问列表和元祖中的元素一样:

empty_dict = {}
d1 = {'a':'some value','b':[1,2,3,4]}
print(d1)  #{'a': 'some value', 'b': [1, 2, 3, 4]}
d1[6] = 'an integer'
print(d1) #{'a': 'some value', 'b': [1, 2, 3, 4], 6: 'an integer'}
print(d1['b']) #[1, 2, 3, 4]

我们也可以用检查列表或元祖中是否含有一个元素的相同语法来检查字典是否含有一个键:

print('b' in d1) #True

我们也可以使用del关键字或pop方法删除值,pop方法会在删除的同时返回被删的值,并删除键:

empty_dict = {}
d1 = {'a':'some value','b':[1,2,3,4]}
d1[5] = 'some value'
d1['dummy'] = 'another value'
print(d1) #{'a': 'some value', 'b': [1, 2, 3, 4], 5: 'some value', 'dummy': 'another value'}
del d1[5]
print(d1) #{'a': 'some value', 'b': [1, 2, 3, 4], 'dummy': 'another value'}
ret = d1.pop('dummy')
print(ret) #another value
print(d1) #{'a': 'some value', 'b': [1, 2, 3, 4]}

keys方法和values方法会分别提供字典键、值的迭代器。但是键值对并没有特定的顺序,这些函数输出的键、值都是按照相同的顺序。
我们也可以使用update方法将两个字典合并:

empty_dict = {}
d1 = {'a':'some value','b':[1,2,3,4],7:'an integer'}
d1.update({'b':'foo','c':12})
print(d1) #{'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}

如果传给update方法的数据也含有相同的键,那么它的值将会被覆盖。

4.1从序列生成字典

通常情况下,我会有两个序列想要在字典中按元素配对:

mapping = {}
for key,value in zip(key_list,value_list):
	mapping[key] = value

由于字典本质上是含有2个元素的元祖的集合,字典是可以接受一个2-元祖的列表作为参数的:

mapping = dict(zip(range(5),reversed(range(5))))
print(mapping) #{0: 4, 1: 3, 2: 2, 3: 1, 4: 0}

4.2默认值

通常情况下会有这样的代码逻辑:

if key in some_dict:
    value = some_dict[key]
else:
    value = default_value

上述的if-else代码块也可以被简写为:

value = some_dict.get(key,default_value)

将字词组成的列表根据首字母分类为包含列表的字典的代码实现有以下三种方式:

words = ['apple','bat','bar','atom','book']
by_letter = {}
for word in words:
    letter = word[0]
    if letter not in by_letter:
        by_letter[letter] = [word]
    else:
        by_letter[letter].append(word)
print(by_letter)  #{'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
words = ['apple','bat','bar','atom','book']
by_letter = {}
for word in words:
    letter = word[0]
    by_letter.setdefault(letter,[]).append(word)
print(by_letter)  #{'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}

内建的集合模块有一个类defaultdict,这个类使得上述实现更为简单:

words = ['apple','bat','bar','atom','book']
by_letter = {}
from collections import defaultdict
by_letter = defaultdict(list)
for word in words:
    by_letter[word[0]].append(word)
print(by_letter)  #defaultdict(<class 'list'>, {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']})

4.3有效的字典键类型

尽管字典的值可以是任何python对象,但键必须是不可变的对象,比如标量类型(整数、浮点数、字符串)或元祖(元祖内对象也必须是不可变对象)。通过hash函数可以检查一个对象是否可以哈希化(即是否可以用作字典的键):

print(hash((1,2,[2,3])))
Traceback (most recent call last):
  File "C:/Users/asus/PycharmProjects/数据分析/3.1数据结构和序列.py", line 159, in <module>
    print(hash((1,2,[2,3])))
TypeError: unhashable type: 'list'

5集合

集合是一种无序且元素唯一的容器。集合可以有两种创建方式:通过set函数或者是用字面值集与大括号的语法:

a = set([2,2,2,1,3,3])
print(a) #{1, 2, 3}
print({1,2,2,3,4,5,5}) #{1, 2, 3, 4, 5}

集合支持数学上的集合操作,python集合操作如下表所示:

函数 描述
a.add(x) 将x元素加入集合a
a.clear() 将集合重置为空,清空所有元素
a.remove(x) 从集合a移除某个元素
a.pop() 移除任意元素,如果集合是空的抛出keyError
a.union(b) 两个集合的联合就是两个集合中不同元素的并集
a.update(b) 将a的内容设置为a和b的并集
a.intersection(b) a、b中同时包含的元素
a.difference(b) 在a不在b的元素
a.difference_update(b) 将a的内容设为在a不在b的元素
a.issubset(b) 如果a包含于b返回True
a.issuperset(b) 如果a包含b返回True
a.isdisjoint(b) a、b没有交集返回True

和字典类似,集合的元素必须是不可变的。如果想要包含列表型的元素,必须先转换为元祖:

my_data = [1,2,3,4]
my_set = {tuple(my_data)}
print(my_set) #{(1, 2, 3, 4)}

当且仅当两个集合的内容一模一样时,两个集合才相等:

print({1,2,3} == {3,2,1}) #True

6列表、集合和字典的推导式

列表推导式的基本形式为:
[expr for val in collection if condition]
这条语句与下面的for循环是等价的:

result = []
for val in collection:
	if condition:
		result.append(expr)

例如,给定一个字符串列表,我们可以过滤出长度大于2的,并且将字母改为大写:

strings = ['a','as','bat','car','dove','python']
print([x.upper() for x in strings if len(x) > 2]) #['BAT', 'CAR', 'DOVE', 'PYTHON']

集合推导式看起来很像列表推导式,只是中括号变成了大括号:
set_comp = {expr for value in collection if condition}

strings = ['a','as','bat','car','dove','python']
unique_lengths = {len(x) for x in strings}
print(unique_lengths) #{1, 2, 3, 4, 6}

使用map函数可以使得代码更为简洁:

strings = ['a','as','bat','car','dove','python']
print(set(map(len,strings))) #{1, 2, 3, 4, 6}

6.1嵌套列表推导式

all_data = [['John','Emily','Michael','Mary','Steven'],
            ['Maria','Juan','Javier','Natalia','Pilar']]
names_of_interest = []
for names in all_data:
    enough_es = [name for name in names if name.count('e') >= 2]
    names_of_interest.append(enough_es)

等价于:

all_data = [['John','Emily','Michael','Mary','Steven'],
            ['Maria','Juan','Javier','Natalia','Pilar']]
result = [name for names in all_data for name in names if name.count('e') >= 2]
print(result) #['Steven']
some_tuples = [(1,2,3),(4,5,6),(7,8,9)]
flattened = [x for tup in some_tuples for x in tup]
print(flattened) #[1, 2, 3, 4, 5, 6, 7, 8, 9]

等价于:

some_tuples = [(1,2,3),(4,5,6),(7,8,9)]
flattened = []
for tup in some_tuples:
    for x in tup:
        flattened.append(x)
print(flattened) #[1, 2, 3, 4, 5, 6, 7, 8, 9]

猜你喜欢

转载自blog.csdn.net/weixin_43303087/article/details/83996409
今日推荐