Python内置数据结构
#####此篇文章来源于我的老师Wayne,仅作记录以备复习
集set
- 约定
set翻译为集合
collection翻译为集合类型,是一个大概念 - set
可变的、无序的、不重复 的元素的集合
set定义 初始化
- set() -> new empty set object
- set(iterable) -> new set object
注意
- s1={}为字典,而不是集合
- s2={[1],(1),1} 错误,set的元素要求必须可以hash
- set 的元素不可以使用索引
- set可以迭代
set增加
- add(elem)
增加一个元素到set中
如果元素存在,什么都不做
- update(*others)
- 合并其他元素到set集合中来
- 参数others必须是可迭代对象
- 就地修改
set删除
- remove(elem)
- 从set中移除一个元素
- 元素不存在,跑出KeyError异常
- discard(elem)
- 从set中移除一个元素
- 元素不存在,什么都不做
- pop() -> item
- 移除并返回任意的元素
- 空集返回KeyError异常
- clear()
- 移除所有元素
set修改、查询
- 修改
- 要么删除,要么加入新的元素
- 查询
- 非线性结构,无法索引
- 遍历
- 可以迭代所有元素
- 成员运算符
- in 和 not in 判断元素是否在set中
- 效率是O(1) ,set用元素的hash值进行查询
set成员运算符的比较
pass
set和线性结构
- 线性结构的查询时间复杂度是O(n),即随着数据规模的增大而增加耗时
- set、dict等结构,内部使用hash值作为key,时间复杂度可以做到O(1),查询时间和数据规模无关
- 可hash
- 数据行int、float、complex
- 布尔型True、False
- 字符串string、bytes
- tuple
- None
- 以上都是不可变类型,是可哈希类型,hashable
- set的元素必须是可hash的
集合
- 基本概念
- 全集
所有元素的集合。例如实数集,所有实数组成的集合就是全集
- 子集subset和超集superset
一个集合A所有元素都在另一个集合B内,A是B的子集,B是A的超集
- 真子集和真超集
A是B的子集,且A不等于B,A就是B的真子集,B是A的真超集
- 并集:多个集合合并的结果
- 交集:多个集合的公共部分
- 差集:集合中出去和其他集合公共部分
集合运算
并集
- 将两个集合A和B的所有的元素合并到一起,组成的集合称作集合A与集合B的并集
- union(*others)
返回和多个集合合并后的新的集合 - | 运算符重载
等同于union - update(*others)
和多个集合合并,就地修改 - |=
等同 update
交集
- 集合A和B,由所有属于A属于B的元素组成的集合
- intersection(*others)
返回和多个集合的交集 - &
等同于 intersection - intersection_update(*others)
获取和多个集合的交集,并就地修改 - &=
等同于 intersection_update
差集
- 集合A和B,由所有属于A且不属于B的元素组成的集合
- difference(*others)
返回和多个集合的差集 - –
等同于difference - difference_update(*others)
获取和多个集合的差集并就地修改 - –=
等同difference_update
对称差集
- 集合A和B,由所有属于A且不属于B的交集元素组成的集合,记做(A-B)U(B-A)
- symmetric_difference(other)
返回和另一个集合的差集 - ^
等同于symmetric_differece - symmetric_difference_update(other)
获取和另一个集合的差集并就地修改 - ^=
等同symmetric_difference_update
- issubset(other)、<=
判断当前集合是否是另一个集合的子集 - set1 < set2
判断set1是否是set2的真子集 - issuperset(other)、>=
判断当前集合是否是other的超集 - set1 > set2
判断set1是否是set2的真超集 - isdisjoint(other)
当前集合和另一个集合有没有交集
没有交集,返回True
字典dict
- key-value 键值对的数据集合
- 可变的、无序的、key不重复
字典dict定义 初始化
- d = dict() 或者 d = {}
- dict(**kwargs) 使用 name=value对 初始化一个字典
- dict(iterable,**kwarg)使用可迭代对象和 name=value对 构造字典,不过可迭代对象的元素必须是一个二元 结构
- d = dict(((1,‘a’),(2,‘b’))) 或者 d = dict(([1,‘a’],[2,‘b’]))
- dict(mapping,**kwarg) 使用一个字典构建另一个字典
- d = {‘a’:10,‘b’:20,‘c’:None,‘d’:[1,2,3]}
- 类方法 dict.fromkeys(iterable,value)
- d = dict.fromkeys(range(5))
- d = dict.fromkeys(range(5),0)
字典元素的访问
- d[key]
- 返回key对应的值value
- key不存在抛出KeyError异常
- get(key[,default])
- 返回key对应的值value
- key不存在返回缺省值,如果没有设置缺省值就返回None
- setdefault(key[,default])
- 返回key对应的值value
- key不存在,添加kv对,value设置为default,并返回default,如果default没有设置,缺省为None
字典增加和修改
- d[key] = value
- update([other]) --> None
- 使用另一个字典的kv对更新本字典
- key不存在,就添加
- key存在,覆盖已经存在的key对应的值
- 就地修改
例:
d.update(red=1)
d.update((('red‘,2),))
d.update({'red':3})
字典删除
- pop(ley[,default])
- key 存在,移除它,并返回它的value
- key 不存在,返回给定的default
- default未设置,key不存在,则抛出KeyError异常
- popitem()
- 移除并返回一个任意的键值对
- 字典为empty,抛出KeyError异常
- clear()
- 清空字典 (一般情况不建议使用)
-
del 语句
a = True
b = [6]
d = {'a':1,'b':b,'c':[1,3,5]}
del a
del d['c']
del b[0]
c=b
del c
del b
b = d['b']
del a[‘c’] 看着像删除了一个对象,本质上减少了一个对象的引用,del 实际上删除的是名称,而不是对象(引用计数减一)
字典遍历(方法列举)
for k in d:
print(k)
for k in d.keys():
print(k)
for k in d:
print(d[k])
for k in d.keys():
print(d.get(k))
for v in d.value():
print(v)
for item in d.items():
print(item)
for item in d.itmes():
print(tiem[0],item[1])
for k,v in d.items():
print(k,v)
for k,_ in d.items():
print(k)
for _,v in d.itmes():
print(v)
字典遍历总结
- Python3 中,keys、values、items 方法返回一个类似一个生成器的可迭代对象,不会把函数的返回结果复制到内存中
- 返回Dictionary view 对象,可以使用 len()、iter()、in 操作
- 字典的 entry 的动态的视图,字典变化,视图将反映出这些变化
- keys返回一个类 set对象,也就是可以看做一个set集合
- 如果values都可以hash,那么iems也可以看做是类set对象
- Python2 中,上面的方法会返回一个新列表,占据新的内存空间。所以Python2 建议使用iterkeys、itervalues、iteritems版本,返回一个迭代器,而不是返回一个copy
字典遍历和移除
- 如何在遍历的时候移除元素
!错误的做法
d = dict(a=1,b=2,c='abc')
for k,v in d.items():
d.pop(k) 会抛异常
while len(d): 相当于清空,不如直接clear()
print(d.popitem())
while d:
print(d.popitem())
正确的做法
d = dict(a=1,b=2,c='abc')
keys = []
for k,v in d.items():
if isinstance(v,str):
keys.append(k)
for k in keys:
d.pop(k)
print(d)
字典的key
- key 的要求和 set 的元素要求一致
- set 的元素可以就是看做key,set 可以看做dict的简化版
- hashable 可哈希才可以作为 key,可以使用 hash()测试
- d = {1:0,2.0:3,‘abc’:None,(‘hello’,‘world’,‘python’):“string”,b’abc’:‘135’}
defaultdict
- collections.defaultdict([default_factory[,…]])
- 第一个参数是default_factory,缺省是None,它提供了一个初始化函数。当key不存在的时候,会调用这个工厂函数来生成key对应的value
- 构造一个字典,value是列表,为其添加随机个元素
import random
d1={}
for k in 'abcdef':
for v in range(random.randint(1,5)):
if k not in d1.keys():
d1[k] = []
d1[k].append(v)
print(d1)
-------------
from collections import defaultdict
import random
d1 = defaultdict(list)
for k in 'abcdef':
for v in range(random.randint(1,5)):
d1[k].append(v)
print(d1)
OrderedDict
- collections.OrderedDict
- key 并不是按照加入的顺序排列,可以使用OrderedDict记录顺序
from collections import OrderedDict
import random
d = {'banana:3,'apple':4,'pear':1,'orange':2}
print(d)
keys = list(d.keys())
random.shuffle(keys)
print(leys)
od = OrderedDict()
for key in keys:
od[key] = d[key]
print(od)
print(od.keys())
- 有序字典可以记录元素插入的顺序,打印的时候也是按照这个顺序输出打印
- 3.6版本的Python的字典就是记录key插入的顺序(IPython不一定有效果)
- 应用场景
- 假如使用字典记录了N个产品,这些产品使用ID由小到大加入到字典中
- 除了使用字典检索的遍历,有时候需要取出ID,但是希望是按照输入的顺序,因为输入顺序是有序的
- 否则还需要重新把遍历到的值排序
Python解析式、生成器
列表解析List Comprehension
- 语法
- [返回值 for 元素 in 可迭代对象 if 条件]
- 使用中括号 [] , 内部是 for 循环,if 条件语句可选
- 返回一个新的列表
- 列表解析式是一种语法糖
- 编译器会优化,不会因为简写而影响效率,反而因优化提高了效率
- 减少程序员工作量,减少出错
- 简化了代码,但可读性增强
举例
- 获取10以内的偶数,比较执行效率
even = []
for x in range(10):
if x % 2 == 0:
even.append(x)
--------------------------
even = [x for x in range(10) if x % 2 == 0]
FAQ
- 有这样的赋值语句 newlist = [print(i) for i in range(10)] ,请问 newlist 的元素打印出来是什么?
这样的元素打印出来是None。由于print的返回值为None,所以列表中会生成10个None
- 获取20以内的偶数,如果同时将3的倍数也打印 [i for i in range(20) if i % 2 ==0 elif i % 3 ==0] 行么?
这样做是不行的,会抛出语法错误。因为列表解析式中不允许出现else语句,想要的到问题的结果,有下面两种做法
[ i for i in range(20) if i % 2 == 0 if i % 3 == 0]
[ i for i in range(20) if i % 2 == 0 and i % 3 == 0]
列表解析式进阶
[ expr for i in iterabe1 for j in iterable2 ]
等价于
ret = []
for i in iterable1:
for j in iterable2:
ret.append(expr)
- 举例
[(x,y) for x in 'abcde' for y in range(3)]
[[x,y] for x in 'abcde' for y in range(3)]
[{x:y} for x in 'abcde' for y in range(3)]
习题举例
- 生成一个新列表,要求新列表元素是lst相邻2项的和
numlist = [1,4,9,16,2,5,10,15]
length = len(numlist)
newnumlist = [ numlist[i]+numlist[i+1] for i in range(length-1)]
print(newlist)
- 用列表解析式打印九九乘法表
[print('{}*{}={:>2} {}'.format(x,y,x*y, '\n' if x == y else''),end='') for x in range(1,10) for y in range(1,x+1)]
生成器表达式
- 语法
- (返回值 for 元素 in 可迭代对象 if 条件)
- 列表解析式的中括号换成小括号就行了
- 返回一个生成器
- 和列表解析式的区别
- 生成器表达式是按需计算(或称惰性求值、延迟计算),需要的时候才计算值
- 列表解析式是立即返回值
- 生成器
- 可迭代对象
- 迭代器
举例
g = ("{:04}".format(i) for i in range(1,11))
next(g)
for x in g:
print(x)
print('~~~~~~~~~~‘)
for x in g:
print(x)
-
总结
- 延迟计算
- 返回迭代器
- 从前到后从头到尾走完一遍以后,不能回头
-
对比列表
g = ["{:04}".format(i) for i in range(1,11)]
for x in g:
print(x)
print(`~~~~~~~~~~~~~~~`)
for x in g:
print(x)
- 总结
- 立即计算
- 返回的不是迭代器,返回的是可迭代对象列表
- 从前到后走完一遍后,可以重新回头迭代
和列表解析式的对比
- 计算方式
- 生成器表达式延迟计算,列表解析式立即计算
- 内存占用
- 单从返回值本身来说,生成器表达式占用当前内存较小,省内存,列表解析式返回新的列表
- 生成器没有数据,内存占用极少,它是使用时一个个返回数据。如果将这些返回值的数据合起来占用的内存也和列表解析式差不多。但是,它不需要立即占用这么多内存
- 列表解析式构造新的列表需要立即占用内存,不管你是否立即使用这么多数据
- 计算速度
- 单从计算时间看,生成器表达式耗时非常短,列表解析式耗时长
- 但是生成器本身并没有返回任何值,只返回了一个生成器对象
- 列表解析式构造并返回了一个新的列表,所以看起来耗时了
集合解析式
- 语法
- {返回值 for 元素 in 可迭代对象 if 条件}
- 列表解析式的中括号换成大括号{}就行了
- 立即返回一个集合
- 用法
- {(x,x+1) for x in range(10)}
- {[x] for x in range(10)} # 注意,这种方法是错误的,因为集合中的元素必须可以hash,列表不可hash,所以语法错误
字典解析式
- 语法
- {返回值 for 元素 in 可迭代对象 if 条件}
- 列表解析式的中括号换成大括号{}就行了
- 使用key:value 形式
- 立即返回一个字典
- 用法
- {x:(x,x+1) for x in range(10)}
- {x:[x,x+1] for x in range(10)}
- {(x,):[x,x+1] for x in range(10)}
- {[x]:[x,x+1] for x in range(10)} #注意,这种方法是错误的,因为字典的key 要求必须是可hash值。列表不可hash,所以语法错误
- {chr(0x41+x):x**2 for x in range(10)}
- {str(x):y for x in range(3) for y in range(4)} # 注意,这里只会输出三个元素,因为作为 key 的 x 只能取到三次
总结
- Python 2 引入列表解析式
- Python 2.4 引入生成器表达式
- Python 3 引入集合、字典解析式,并迁移到了 2.7
- 一般来说,应该多应用解析式,简短、高效
- 如果一个解析式非常复杂,难以读懂,可以考虑拆解成for循环
- 生成器和迭代器是不同的对象,但都是可迭代对象
- 可迭代对象范围更大,都可以使用 for 循环遍历