欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

python 学习之 数据结构 列表(list)和元组(tuple)字典 (dict)和 集合(set) 以及各自性能

程序员文章站 2024-02-12 13:18:16
...

列表和元组

基本使用
在python 中 列表和元组都是可以放任意数据类型的集合 放的数据类型也可以不同
比如如下代码

l = [1,3,4,5,'5'] 列表l同时含有int 和 string类型的数据
t = (1,2,3,'jsaon') 元组t同时含有int 和 string类型的数据

二者区别

  1. 声明方式 列表是一[]形式的 元组是以 () 形式的
  2. 列表是动态的 可以随时进行增删改查 元组是不可变的 就是一旦声明就不会再改变。比如如下例子
l = [1,3,4,5,'5'] 
l[2] = 50 # 可以方便的将第二个数据改成50 结果就是 [1,3,50,5,'5']  计算机里都是从0开始数的
t = (1,2,3,'jsaon') # 修改t 程序会报错
# t[2] = 50 # 修改t 程序会报错 TypeError: 'tuple' object does not support item assignment

当我们向列表或者元组中增加数据时又是怎样的呢
因为列表是动态的 所以直接可以如下操作 直接append 增加

l = [1,3,50,5,'5'] 
l.append(100)
print(l) # [1,3,50,5,'5',100] 

元组是不可变的 所以我们向元组中添加数据相当于是开辟了一块新的内存

t = (1,2,3,'jsaon')
t = t + (5,)
print(t) # (1,2,3,'jsaon',5)

列表和元组基本使用和注意事项

  1. 列表和元组支持负索引
l =  [1,3,50,5,'5',100] 
print(l[1]) 打印出最后一个元素 打印出第一个元素 3
print(l[-1]) 打印出最后一个元素 100 一次类推 -2 就是倒数第二个
同样元组也可以进行同样的操作
t = (1,2,3,'jsaon')
t[-1] # 'jsaon'
  1. 列表和元组支持切片索引
l = [1,2,3,4,5]
l[1:3] # 切片索引 从索引1取到索引3 左闭右开 也就是获得索引 1和 2
t = (1,2,3,4,5)
t[1:3] # 跟列表一样的结果
  1. 列表和元组可以随意嵌套
l = [[3,4,5],[1,555,7]]
t = ((3,4),(78,65))
  1. 列表和元组相互转换
l = [3,5,6,7,3,32,1]
t = tuple(l) 将列表转化成元组 (3, 5, 6, 7, 3, 32, 1)
t = (55,34,32,6,7)
l = list(t) # 将元组转化成列表[55, 34, 32, 6, 7]
  1. 列表和元组常用内置函数
l = [3,5,6,7,3,32,1]
l.count(3) # 获得l中3 的个数 这里结果是 2
l.index(7) # 获得第一个元素为7的索引
l.sort() 排序 [1, 3, 3, 5, 6, 7, 32]
l.reverse() 反转 [32, 7, 6, 5, 3, 3, 1] 
元组操作
t = (45,6,7,3,2,8,1,0)
元组没有内置的 sort()  和  reverse()  但是count和 index可以直接用
new_t = sorted(t) #[0, 1, 2, 3, 6, 7, 8, 45]
list(reversed(t)) # [0, 1, 8, 2, 3, 7, 6, 45]
sorted reversed 都可以直接作用在列表或者元组上 只是这两个方法会直接返回一个新的列表或元组

列表和元组存储方式的差异

l = [1, 2, 3]
s_l = l.__sizeof__()
print(s_l) # 64
tup = (1, 2, 3)
tup_l = tup.__sizeof__()
print(tup_l) # 48
__sizeof__() 返回对象所占的内存字节数
从上边可以看到列表和元组保存相同的内容 但是列表比元组多出 16和字节
l中存储的是int类型 占8个字节 但是 由于列表是动态的要保存 指针的大小 和扩展出的大小 分别是 8 所以多出16个字节 下面看详细的例子
l = []
l.__sizeof__() // 空列表的存储空间为 40 字节
40
l.append(1)
l.__sizeof__() 
72 // 加入了元素 1 之后,列表为其分配了可以存储 4 个元素的空间 (72 - 40)/8 = 4
l.append(2) 
l.__sizeof__()
72 // 由于之前分配了空间,所以加入元素 2,列表空间不变
l.append(3)
l.__sizeof__() 
72 // 同上
l.append(4)
l.__sizeof__() 
72 // 同上
l.append(5)
l.__sizeof__() 
104 // 加入元素 5 之后,列表的空间不足,所以又额外分配了可以存储 4 个元素的空间
从上边的例子可以看出 每放入一个元素 列表都会多初始化一部分内存供使用 可以提高效率  而元组的大小就是一定的 初始化之后不会再变

元组和列表的性能
初始化: 命令行输入 如下命令 可见 元组初始化 比 列表初始化快5倍
python3 -m timeit ‘x=[1,2,3,4,5]’
5000000 loops, best of 5: 67.3 nsec per loop
python3 -m timeit ‘x=(1,2,3,4,5)’
20000000 loops, best of 5: 14.3 nsec per loop
索引获取:元组的索引获取也比列表略快 但是很少
python3 -m timeit ‘x=[1,2,3,4,5]’ ‘y=x[3]’
5000000 loops, best of 5: 97.5 nsec per loop
python3 -m timeit ‘x=(1,2,3,4,5)’ ‘y=x[3]’
10000000 loops, best of 5: 38.3 nsec per loop
使用场景
固定不变的 那就用元组 需要增删改查的就用列表

字典和集合

字典:由一系列键值对组成的元素的集合 相比较于列表了元组 字典的性能更优,特别是对于增删改查字典都能在常数复杂度内完成。
集合: 集合和字典基本相同 不同点是 集合没有键值对。是一组无序的 无重复的元素组合
字典和集合的声明方式

d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}
d2 = dict({'name': 'jason', 'age': 20, 'gender': 'male'})
d3 = dict([('name', 'jason'), ('age', 20), ('gender', 'male')])
d4 = dict(name='jason', age=20, gender='male') 
d1 == d2 == d3 ==d4
True
s1 = {1, 2, 3}
s2 = set([1, 2, 3])
s1 == s2
True

字典中元素的访问

d = {'name': 'jason', 'age': 2,'money':100}
***获取方式一***
value = d['name']  结果是 'jason'  
注意 如果这里取一个不存在的键 那么就会报错比如 
d['er'] 由于er不存在 所以会报 KeyError: 'er'
***获取方式二***
value = d.get('name') 此时和d['name']是一样的 当访问一个不存在的键时
value = d.get('er') 结果会返回None。不会报错 也可以指定一个默认值
value = d.get('er','123') 结果就是默认值 123

集合中元素的访问
比如 s = {1,2,3,4} s[0] 这样的操作会报错 因为集合也是一个hash表 并不像列表 不能通过索引值直接获得
判断元素在不在字典或集合中 用关键字 in

s = {1, 2, 3}
1 in s # True
10 in s  #False
d = {'name': 'jason', 'age': 20}
'name' in d  # True
'location' in d # False

字典和集合的增删改查

字典
d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}
d1 =['sex'] = 1  # 增加
d1.pop('name') #删除 键name. {'age': 20, 'gender': 'male'}
d1['name'] = 'zuozhe'# 修改键name 的值为zuozhe
d1['name']# 获得键name的值
集合
s = {'name','age','money'}
s.add('work') # {'age', 'money', 'name', 'work'} 增加元素 work
s.remove('name') # 删除元素 name

集合字典排序

字典排序
d = {'b': 1, 'a': 2, 'c': 10}
sort_by_key = sorted(d.items(),key=lambda x:x[0]) # 按照key 进行排序 升序  [('a', 2), ('b', 1), ('c', 10)]
sort_by_value = sorted(d.items(),key=lambda x:x[1]) # 按照value 升序 [('b', 1), ('a', 2), ('c', 10)]
注意这里的排序结果是放着元组的列表 元组是由键和值组成的
集合排序
s = {3, 4, 2, 1}
sorted(s) # 对集合的元素进行升序排序 返回的是一个 列表
[1, 2, 3, 4]

字典和集合性能

  1. 字典查找比列表查找快 假设我们要根据商品id获得商品的价格 那么 列表循环查找的方式显然不恰当。 下边举例来说
生成一个 列表 元组内容是例如 (0,0) (1,10) (2,20) 的数据 接下来我们获得id是 500000 的商品价格
import time
x = [(x,x*10) for x in range(1000000)]
t1 = time.perf_counter()
for a,v in x:
    if a == 500000:
        print(v)
        break
t2 = time.perf_counter()
t2 - t1 时间差是 0.054028294998715864
用字典的形式 
x2 = dict(x) 将数组转化成字典的形式
t1 = time.perf_counter()
print(x2[500000])
t2 = time.perf_counter()
t2 - t1 时间差是 0.00013000299804843962 可见1000000数据量级时 字典比列表快了。几百倍
  1. 将需要去重统计时使用集合更好 下边举例说明
同样生成1000000条数据
x = [a for a in range(1000)]
x1 = []
for _ in range(1000):
    x1.extend(x)
len(x1) 1000000。有重复数据
统计不重复的数据的个数
t1 = time.perf_counter()
xx = []
for x in x1:
    if x not in xx:
        xx.append(x)
t2 = time.perf_counter()
t2 - t1. 时间很惊人 用了 7.215088085999014 
下边改为 set的方法
t1 = time.perf_counter()
s = set()
for x in x1:
    s.add(x)
t2 = time.perf_counter()
t2 - t1 时间惊人的少 仅用了 0.11796300100104418  可见选对数据结构有多么重要

字典和集合的工作原理
字典和集合都是一张hash表 对于字典而言存储的是 hash值 键值的结构 对于集合来讲就是没有键值 只有单一的元素
插入原理
当向集合或者字典中插入数据时 系统会先计算 键的hash值 再计算应该插入hash表的位置 如果当前位置为空就直接放入 如果位置已经被占用,python会比较两个元素的hash值和键值是否相等 如果相等就更新 如果两着有一个不相等 这种情况叫做hash冲突 就是hash值相等但是值不相等 ,这是python会继续寻找一个空位置 存储
查找原理
和插入一样 python首先根据hash值 找到相应的位置,然后比较hash值和键值是否相等 如果相等就直接返回 不相等就继续向下找 找不到就抛出异常
删除操作
对于删除操作 实际上是先对要删除的位置赋一个特殊值 等待下次调整hash表的大小时进行删除。

总结

列表 是动态的 可以进行增删改查 性能上会略差于元组
元组 初始化之后就不能再变 就算是添加操作实际上也是新生成一个元组

python 中初始化列表的方式
l = list()
l = []
这两个那个更好 
python3 -m timeit 'x=[]'
10000000 loops, best of 5: 25.7 nsec per loop
python3 -m timeit 'x=list()'
2000000 loops, best of 5: 119 nsec per loop
从效率上来看 [] 的效率更高些 其实直接使用[] 是调用的c函数 所以会更快一些

字典和集合 是以hash表的方式存放的 当执行插入操作时会先根据键计算出hash值并计算出元素应该插入的位置。