数据结构

序列

1.内置序列类型概述

容器序列

容器序列:可以存放不同类型的数据(存放数据的引用(对象))

list、tuple、collections.deque

扁平序列

扁平序列:只能存放同种数据类型,存放的是对象的值,不是引用,扁平序列通常是一段连续的内存空间

str、bytes、bytearray、array.array、memoryview

可变序列

可以进行增删改的序列

list、collections.deque、bytearray、array.array、memoryview

不可变序列

不能被修改

tuple、str、bytes

2.列表推导和生成器表达式

列表推导

如果只是单纯的生成一个列表,那么建议使用了列表推导,而不是for循环

列表推导实例:

my_list1 = [str(i) for i in range(10)]
my_list2 = [i for i in range(10) if i>5]
my_list3 = [(color, size) for color in colors for size in sizes]
  • 列表推导适用与代码量不大(不超过一行),且单纯为了生成列表或其他类型序列
  • 列表推导中的变量在python3中不存在变量泄露的问题

生成器表达式

生成器表达式是列表推导的进阶版,它背后遵循了迭代器的原则,会逐个生成元素,而不是直接先建立一整张完整的列表,利用这种方式很好的节省了内存空间

意思就是当我需要的时候元素才会生成,而不是一开始就生成所有的元素,这样造成了内存的浪费

生成器表达式和列表推导语法差不多,只是将方括号换成了圆括号

生成器表达式实例:

my_generator = ('%s %s'%(color,size) for color in colors for size in sizes)

生成器表达式,只在每次for循环运行时产生一个元素,如果colors、sizes中有大量的数据,那么生成器可以很好的节省空间

3.元组

元组作为记录使用

说到元组,第一印象就是不可变序列,不能被修改(可以删除整个元组),元素位置不能变,

但是元组除了是不可变序列以外,它还可以用于记录(联想数据库中的记录)

记录的位置是不能变的,也就是说元组的元素位置是有意义的,不能改变

元组实例:

my_tuple = ('yangdaxian', 18, 'man')

上面的示例元组可以理解为数据库中的一张表,表中有3个字段分别为:姓名,年龄,性别,

而这里my_tuple元组就表示表里的一条记录。

所以说,元组可以完美的被当作记录来使用

students = [('yangdaxian', 18, 'man'), ('xiaoluo', 17, 'woman'), ('tingting', '1', 'woman')]
print(sorted(students))
for name, _, _ in students:
    print(name)

#输出结果
[('tingting', '1', 'woman'), ('xiaoluo', 17, 'woman'), ('yangdaxian', 18, 'man')]
yangdaxian
xiaoluo
tingting

Process finished with exit code 0

元组拆包

元组拆包可以应用到任何可迭代对象上

实例:

people = ('yangdaxian', 18)
name, age = people
print(name, age)
#输出
yangdaxian 18
a=3
b=4
a,b=b,a	#直接交换
print(a,b)
#输出
4 3
def add(a,b):
    return a+b

demo_data=(4,5)
print(add(*demo_data))#作为函数的参数,可以用*解包

#输出
9
name, _ = ['yangdaxian',18]	#_用作占位符,数组也可以拆包,只要是可迭代对象都行
print(name)
#输出
yangdaxian
用*来处理剩下的元素
a,b,*rest=range(5)		#*后面必须接变量名
print(rest)

#输出
[2, 3, 4]

a,b,*rest=range(2)
print(rest)

#输出
[]
嵌套元组拆包
demo=[('yzz',18,('sing', 'play')),('luo',18,('eat','dance')),('ting',3,('milk','sleep'))]
for name,age,(hobby1,hobby2) in demo:#这里遍历数组demo,拆包的时候注意要一一对应
    print(hobby1,end=' ')
    
#输出
sing eat milk
具名元组(带有字段的记录)

collections.namedtuple

前面说到可以将元组看为一个记录,但是记录是有字段的,这个用元组怎么解决呢?

答案就是使用具名元组

  • 使用具名元组实例化的对象与元组本身在内存中的大小是一样的
  • 使用具名元组实例化的对象比普通对象还要小一些

说白了具名元组可以快速简单的创建一个类

实例:

from collections import namedtuple

#namedtuple的第一个参数是类名,第二个参数是类中的字段名,也就是属性名,可以是一个字符串以空格隔开,也可以是一个可迭代序列,如['name','age','sex','hobbys'],但是一般直接写成'name age sex hobbys'这种方式比较方便
#注意这里的name、age、sex、hobbys仅仅只是名字,当实例化时可以任意指定数据类型
#就像name用的字符串'yangdaxian',age用的整型18,hobbys用的列表
People=namedtuple('People','name age sex hobbys')
p1=People('yangdaxian',18,'man',['programme','playgame'])
p2=People('ting',1,'woman',['eat','sleep'])
print(p1,p2,sep='\n')
print(p1.hobbys)	#直接对象.字段名的方式即可获取属性值

#输出
People(name='yangdaxian', age=18, sex='man', hobbys=['programme', 'playgame'])
People(name='ting', age=1, sex='woman', hobbys=['eat', 'sleep'])
['programme', 'playgame']
具名元组的专有属性
#类属性	_fields
#类方法	_make(iterable)	作用:接受一个可迭代对象来生成一个类的实例
#实例方法	_asdict()	作用:将类的实例转换成一个有序字典对象(OrderedDict)

例子:

print(People._fields)	#类属性
print('=====================')
demo_01=('tingting',1,'woman',['eat','sleep'])
p3=People._make(demo_01)	#类方法
print(p3)
print('===========================')
print(p3._asdict())	#实例方法

#输出
('name', 'age', 'sex', 'hobbys')
=====================
People(name='tingting', age=1, sex='woman', hobbys=['eat', 'sleep'])
===========================
{'name': 'tingting', 'age': 1, 'sex': 'woman', 'hobbys': ['eat', 'sleep']}

元组作为不可变列表使用

元组可以作为不可变列表,除了增删改之外的其他方法,元组和列表基本相同

4.切片

切片为什么会忽略最后一个元素

本质就是为了方便人类理解

reason1:

当只有最后一个位置信息时,我们可以快速看出切出来的序列长度

a=[i for i in range(10)]
b=a[:4]	#看到最后一个位置为下标4,那么立即可以得出b的长度为4
print(b)

#输出
[0, 1, 2, 3]

reason2:

当起始、结束位置信息都有时,可以快速看出切片的长度

a=[i for i in range(10)]
b=a[2:5]	#直接5-2=3,得出b的长度为3
print(b)

#输出
[2, 3, 4]

reason3:

可以方便的用任意一个下标分隔序列

a=[i for i in range(10)]
b=a[:4]
c=a[4:]
print(b,c,sep='\n')

#输出
[0, 1, 2, 3]
[4, 5, 6, 7, 8, 9]

有间隔的切片

a='mylyovlepoaevute'
print(a[::3])	#从起点开始切,每间隔3个取后面那个元素

#输出
mylove

多维切片

python内置序列类型都是一维的,并不支持多维,但是python扩展库NumPy支持

用法:

import numpy

a = numpy.arange(12)
print(a)
print(a.shape)
a.shape = 3, 4
print(a)
print(a[:, 0])	#[]里面有逗号表示多维切片,这里是二维

#输出
[ 0  1  2  3  4  5  6  7  8  9 10 11]
(12,)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
[0 4 8]
…在多维切片中的使用

比如有一个四维数组

a=[i,:,:,:]#可以这么切
a=[i,...]#也可以这么切,注意这里...是3个英文句号,不是省略号

给切片赋值

a = [1, 2, 3, 4, 5, 6]
print(a)
a[2:5] = [100, 200]	#给切片赋值,右边必须是一个可迭代序列而不能是一个单一的元素
print(a)		   #如:a[2:5]=100,会报错


#输出
[1, 2, 3, 4, 5, 6]
[1, 2, 100, 200, 6]#只要右边是一个可迭代序列,那么即使元素比左边的切片元素少,后面少					的元素默认赋值给切片为空

5.对序列使用+和*

+和*两边的序列类型必须一样

使用+和*拼接后会生成一个新的序列,不会影响原序列

a = 'abc' * 2 + 'love'
print(a)

#输出
abcabclove

初始化由列表组成的列表

当使用*初始化由列表组成的列表时,会有个坑需要注意

my_list = [[1, 2]] * 3
print(my_list)

#输出
[[1, 2], [1, 2], [1, 2]]

这里初始化后得到的列表里实际上存放的是3个引用,并且这3个引用都是指向同一个列表

列表的引用和新的列表
  1. 使用*初始化列表的列表实际上是放的内部列表的引用,如:
#例1
a = [['_'] * 3] * 3
print(a)

a[0][2] = 'X'
print(a)

#输出
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]

上面,给a的第一行第3列元素赋值,发现内部列表中所有对应位置都被赋值了,这个问题就说明了此时的内部列表是对同一个对象的引用(说白了就是那一个列表),所有才会造成这种情况

例1的本质用下面代码可以解释:

a = []
b = ['_']*3
for i in range(3):
    a.append(b)
print(a)
a[0][2] = 'X'
print(a)

#输出
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]
  1. 使用列表推导初始化列表的列表,生成的内部列表就不是引用,而是新的列表
#例2
a = [['_'] * 3 for i in range(3)]
print(a)
a[0][2] = 'X'
print(a)

#输出
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', 'X'], ['_', '_', '_'], ['_', '_', '_']]

使用列表推导,每次都会生成一个新的内部列表,不是引用同一个列表

例2原理如下:

a = []
b = ['_']
for i in range(3):
    a.append(b * 3)
print(a)
a[0][2] = 'X'
print(a)

#输出
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', 'X'], ['_', '_', '_'], ['_', '_', '_']]

这里每次b*3相当与生成一个新的列表

6.+=和*=

+=和*=都是就地修改原始序列

可变序列2个操作都支持,不可变序列支持*=,但是不支持+=

#可变序列
a = [1, 2]
print(id(a))
a *= 2
print(id(a))

#输出
1649802291776
1649802291776

结果还是原来的对象

#不可变序列
a = (1, 2)
print(id(a))
a *= 2
print(a)
print(id(a))

#输出
1945920343168
(1, 2, 1, 2)
1945920646256

结果是一个新的对象,因为不可变序列是不可以被改变的,所以只能生成一个新的对象

一个经典的例子

>>> a = (1,2,[3,4])
>>> a[2]*=2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> a
(1, 2, [3, 4, 3, 4])
#这里给元组赋值会报错,但是a确实已经被修改了
#原因是因为上面的操作是会先修改列表,这个是被允许的,但是后面一步是给元组赋值,所以会报错,同时a也被修改了

7.list.sort()和sorted()

list.sort()会就地排序,返回None,也就是说不会产生新的序列

sorted()会在原序列的基础上排序,会产生(返回)一个新的序列,它接受任何的可迭代序列,包括不可变序列

不可变序列其实可以排序,只不过原序列还是不变的,但是利用sorted()生成一个新的排好序的序列

因为list.sort()会就地修改原序列,所以不能用在不可变序列,如元组,

但是sorted()会产生一个新的序列,所以可以用于不可变序列进行排序

a = (2, 5, 7, 18, 8)
b = sorted(a)
print(a)
print(b)

#输出
(2, 5, 7, 18, 8)	#元组a还是没变
[2, 5, 7, 8, 18]

使用关键字len,根据元素长度排序,如果2个或多个元素长度相同,那么会按照原序列的位置排序

hobbys = ['eat', 'sleep', 'sing', 'dance']
print(sorted(hobbys, key=len))

#输出
['eat', 'sing', 'sleep', 'dance']

8.利用bisect管理有序序列

使用bisect(序列,a),前提是改序列已经排好序,且是升序排列

本质是使用了二分查找法

这个方法的作用是在序列中找出a应该在序列中的位置(且要满足如果插入a到序列,序列还要保持升序),返回一个索引值

import bisect

haystack = [1, 4, 5, 8, 10, 24, 24, 30]
print(bisect.bisect(haystack, 9))#bisect.bisect()的作用就是实现快速查找

#结果
4
bisect.bisect_right()	#如果要找的值与序列中某个值相同,那么返回右边的索引,bisect.bisect()默认就是bisect.bisect_right()
bisect.bisect_left()	#如果要找的值与序列中某个值相同,那么返回左边的索引

bisect比index快

使用bisect.insort()插入元素

bisect.insort(seq, item),向seq序列中插入元素item,使用该函数在已排序的序列中插入元素后,序列任然保持顺序

import bisect

my_list=[2,5,8,9,13]
bisect.insort(my_list, 6)
print(my_list)

#输出
[2,5,6,8,9,13]

9.数组(取代只包含数字的列表)

如果需要一个只包含数字的列表,那么可以使用数组array.array()

因为数组背后原理是,它的元素不是对象,而是数字的机器翻译,即字节,所以数组的效率比列表高许多

数组还可以直接将数据写入一个文件,也可以从一个二进制文件中读取数据

例子:

from array import array
from random import random

my_array1 = array('d', (random() for i in range(10**7)))#d表示双精度浮点型
print(my_array1[-1])
fp = open('./data.bin', 'wb')#新建文件(这里不用你事先建好该文件)
my_array1.tofile(fp)#写入文件
fp.close()

fp = open('./data.bin', 'rb')
my_array2 = array('d')#建一个空的数组
my_array2.fromfile(fp, 10**7)#从文件读取数据,读取10**7行数据
print(my_array2[-1])
#------------------------------------------------------------------------
a = array('b')#新建一个类型码为b(表示有符号的字符,signed char)的数组a
a.typecode#获取数组a的元素类型码

10.内存视图

memoryview是一个内置类,它能够在不复制内容的情况下操作同一个数组的不同切片,可以实现在数据结构之间的共享内存

numbers = array.array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)#得到一个numbers的内存视图对象
memv_oct = memv.cast('B')#将memv里的内容转换成B类型,即无符号字符
print(memv_oct.tolist())
memv_oct[5] = 4
print(numbers)

#输出
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
array('h', [-2, -1, 1024, 1, 2])#数据改变了,但是numbers的类型没变

11.NumPy和SciPy(工业级数学计算)

NumPy和SciPy都是第三方库,SciPy基于NumPy

例子:

import numpy
a = numpy.arange(12)
print(a)
print(a.shape)
a.shape = 3,4	#将a转换为一个3行4列的二维数组
print(a)
print('=========================')
print(a.transpose())	#行列颠倒,此时数组a并没有变
print('=========================')
print(a)

#输出
[ 0  1  2  3  4  5  6  7  8  9 10 11]
(12,)	#一个有12个元素的一维数组
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
=========================
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]
=========================
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

NumPy读取和写入数据

import numpy
floats = numpy.loadtxt('xxx.txt')#加载一个数据文本文件的内容到floats
numpy.save('my_numpy', floats)#将floats变量里的数据写入一个my_numpy.npy的文件(这个文件可以不用先建立,会自动生成),这里文件后缀名默认为.npy,可以不写

floats2 = numpy.load('my_numpy.npy', 'r+')#读取my_numpy.npy文件的内容到floats2,这里的load使用了内存映射的技术(不知道是不是'r+'的原因),让我们在内存不足时也能对数组做切片操作

12.队列(取代列表的又一种类型)

from collections import deque

dq = deque(range(10), maxlen=10)#maxlen是一个可选参数,代表这个队列可以容纳的元素的数量,而且一旦设定,这个属性就不能修改了
print(dq)

dq.rotate(3)#队列的旋转操作接受一个参数n,当n>0时,队列的最右边的n个元素会被移动到队列的左边
print(dq)
dq.rotate(-3)#当n<0时,队列的最左边的n个元素会被移动到右边
print(dq)

dq.appendleft(-1)#当给队列的头部添加元素时,尾部的元素会被删除
print(dq)

dq.extend([20, 30, 40])#在尾部添加元素,会挤掉头部的元素
print(dq)
dq.extendleft([10, 11, 12])#在头部添加元素,这里时是把迭代器里的元素逐个添加到头部,所以迭代器的元素会逆序出现在队列里
print(dq)


#输出
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8], maxlen=10)
deque([2, 3, 4, 5, 6, 7, 8, 20, 30, 40], maxlen=10)
deque([12, 11, 10, 2, 3, 4, 5, 6, 7, 8], maxlen=10)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值