第一章 数据结构和算法
1.2 可迭代对象的解压
用*号解压未知个数的参数;
解压出来的结果总是列表类型。
1.3 保留最新的几个元素
from collections import deque
q = deque(maxlen=n)
q.append()
不赋值会创建无限大小的队列, 可进行两端的操作。
队列两端的添加删除操作会比列表快.(O(1),O(n))。
q = deque()
q.append()
q.appendleft()
q.pop()
q.popleft()
1.4 找最大或最小的n个元素
import heapq
# 找到数据集中最大最小的n个元素
heapq.nlargest(n, iterable, key=None)
heapq.nsmallest(n, iterable, key=None)
# 把堆中最小的元素弹出
heapq.heappop()
n=1时:max(),min()效率最高;
n较小时:用heapq;
n接近于数据集的大小:用sorted(iterable, key)[:n]
1.5 实现一个优先级队列
import heapq
class PriorityQueue():
def __init__(self):
self._queue = []
self._index = 0
def push(self, item, priority):
heapq.heappush(self._queue, (-priority, self._index, item))
self._index += 1
def pop(self):
return heapq.heappop(self._queue)[-1]
优先级用负是为了让优先级最大的处于堆顶部;
index 支持同等优先级的元素的比较。
1.6 字典中的键映射多个值
把多个值放到容器中。根据需要,列表或集合
from collections import defaultdict
d = defaultdict(list)
d['a'].append(1)
d['b'].append(3)
d['a'].append(4)
d = defaultdict(set)
d['a'].add(1)
defaultdict 会自动为访问的键创建映射,不管键是否存在;
setdefault 可用于普通字典,可对不存在的键人为赋初值。
d = {}
d.setdefault('a', []).append(1)
d.setdefault('b', []).append(3)
d.setdefault('a', []).append(4)
d.setdefault('a') # 返回键对应的值
d.setdefault('d') # 默认赋初值 None
1.7 字典排序
collections 中的 OrderedDict 会按键插入顺序维护一个链表,重复赋值不会改变键的顺序
大小是普通字典的两倍
1.8 字典的运算
用 zip() 翻转键和值后再进行运算。
zip(d.valuues(), d.keys())
zip() 函数生成的是只能使用一次的迭代器。
1.9 查找两字典的相同点
字典的 keys(),items() 方法得到的对象,支持集合操作
a.keys() - b.keys()
a.keys() & b.keys()
a.keys() | b.keys()
a.items() & b.items()
1.10 删除序列相同元素并保持顺序
序列元素可哈希(数值、字符串、元组):
a = [1,5,3,1,9,3,4]
def dedupe1(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
list(deque1(a))
序列元素不可哈希(字典、列表、集合):
b = [{'x':1, 'y':2},{'x':1, 'y':3},{'x':2, 'y':2}]
def dedupe2(items, key=None):
seen = set()
for item in items:
val = item if key is None else key(item)
if val not in seen:
yield item
seen.add(val)
list(dedupe2(b, key=lambda i: i['x']))
list(dedupe2(b, key=lambda i: (i['x'], i['y']))
1.11 命名切片
a = '123456789abcdefg'
print(a[5:10])
# 能增加可读性
data_slice = slice(5, 10)
print(a[data_slice])
# 获取 slice 的信息
data_slice.start # 5
data_slice.stop # 10
data_slice.step # None
# indices 方法能避免下标越界
data_slice = slice(5, 100)
data_slice.indices(len(a)) # (5, 16, 1)
for i in range(*data_slice.indices(len(a))):
print(a[i])
1.12 序列中出现次数最多的元素
from collections import Counter
a = 'abab'
b = 'abc'
aa = Counter(a) # Counter({'a': 2, 'b': 2})
bb = Counter(b) # Counter({'a': 1, 'b': 1, 'c': 1})
aa-bb # Counter({'a': 1, 'b': 1})
aa + bb # Counter({'a': 3, 'b': 3, 'c': 1})
aa.update(b) # 等价于 aa = aa + Counter(b)
1.13 通过某个关键字排序一个字典列表
from operator import itemgetter
a = [{'a':2, 'b':4, 'c': 'ax'},
{'a':1, 'b':2, 'c': 'mj'},
{'a':5, 'b':6, 'c': 'e'}]
res = sorted(a, key=itemgetter('a'))
res = sorted(a, key=itemgetter('a', 'b'))
itemgetter('a')(a[0]) # 2
itemgetter('a', 'b')(a[0]) # (2, 4)
等价于 lambda x: (x[‘a’], x[‘b’]),但比 lambda 更快。
1.14 排序不支持原生比较的对象
from operator import attrgetter
用法和优点类似 itemgetter 只是作用于对象
1.15 通过某个字段将记录分组
data = [{'a':'x01', 'y':'2019'},{'a':'x02', 'y':'2020'},{'a':'x03', 'y':'2019'}]
from itertools import groupby
from operator import itemgetter
data.sort(key=itemgetter('y'))
for y, aa in groupby(data, key=itemgetter('y')):
print(y)
for a in aa:
print(' ', a)
"""result
2019
{'a': 'x01', 'y': '2019'}
{'a': 'x03', 'y': '2019'}
2020
{'a': 'x02', 'y': '2020'}
"""
groupby 会扫描整个序列找出连续的相同值,返回该值和一个迭代器对象;
只检查连续的元素,所以必须事先排好序;
把结果存在 defaultdict 创建的多值字典中便于访问。
1.16 过滤序列元素
一般用列表推导式;
数据量大又不想太耗内存用生成器表达式;
如果过滤规则复杂,就用 filter() 函数。
data = [n for n in range(1000) if n%2==0]
def rules(num):
if num % 3 == 0 and num % 7 == 0:
return True
filter(rules, data)
不符合条件的值替换为新值:
data = [n if n%2==0 else n-1 for n in range(1000)]
想用一个序列的值来过滤另一个序列:
from itertools import compress
names = ['Wilson', 'Sandy', 'Carrol', 'Dante', 'Aaron']
heights = [181, 167, 175, 178, 173]
bool_list = [i>175 for i in heights]
compress(names, bool_list) # 返回迭代器
1.17 从字典中提取子集
# 字典推导式(最快)
dict2 = {key:value for key,value in dict1.items() if key.isdigit()}
# 元组转字典
dict2 = dict((key, value) for key,value in dict1.items() if key.isdigit())
# 先过滤键再取值
dict2 = {key:dict1[key] for key in dict1.keys() if key.isdigit()}
1.18 映射名称到序列元素
想通过名称来访问列表或元组中的元素。collections.namedtuple 能满足这个需求。这个方法返回标准元组类型的子类。
使用命名元组时需要传递一个类名和一个字段名序列,然后返回一个类;
实例化的时候传入对应长度的值序列,访问这些值的方式和访问实例的属性一样。
from collections import namedtuple
Cal = namedtuple('Cal', ['name', 'shares', 'price'])
total = 0
for rec in records:
s = Cal(*rec)
total += s.shares * s.price
命名元组的使用能避免可读性低的下标操作。
命名元组能作为字典的替代,它比字典省内存且高效,支持索引和解压操作,但是命名元组是不可改的。想要改变实例属性,能用 _replace 方法返回一个新的命名元组。
c = Cal('banana', 36, 3.0)
c = c._replace(shares=38)
# Cal(name='banana', shares=36, price=3.0)
如果需要更新很多实例,不适合用命名元组。
1.19 转换并同时计算数据
在序列上执行聚集函数前需要先加工序列时,生成器表达式简洁又高效。
函数调用时直接输入表达式,不需要多加一个括号。
sum(i for i in range(1001) if i%2!=0)
1.20 合并多个字典或映射
从多个字典中,查找值或确定键是否存在。
from collections import ChainMap
a = {'a':1, 'b':3, 'x':5}
b = {'a':3, 'c':6, 'y':8}
c = ChainMap(a, b)
# 先从a中查找,找不到再去b中找。所有字典都找不到会报错。
c['a'] # 1
c['c'] # 6
对 ChainMap 的更新或删除操作只会影响第一个字典。
原字典更新时,ChainMap 也会有相应的变化。