第1章 数据结构和算法:
1.1 将序列分解为单独的变量
核心点:任何序列或可迭代对象,不仅包括元组tuple,列表list,还包括字符串string,文件,迭代器iterator以及生成器generator,都可以通过简单的赋值操作来分解为单个的变量。
如:
s = 'Hello'
a,b,c,d,e =s
print(a) # 'H'
print(b) # 'e'
以及更为复杂的:
data = ['ACME',50,91.1,(2012,12,21)] #含有元组元素的列表
name, shares ,price, date = data #
print(date) # (2012,12,21)
#或者更为详细的赋值:
name,shares,price,(year,month,day) = data
print(month) # 12
需要注意的是:元素的数量必须互相匹配,否则会报错:
p = [4,5]
x,y,z = p #ValueError: need more than 2 values to unpack
*丢弃某些特定值的方法:python中没有提供相应的语法,但可以通过选一个用不到的变量名来实现。
例:
data = ['ACME',50,91.1,(2012,12,21)]
_, shares, price, _ = data
print(shares) #50
print(price) #91.1
1.2 从任意长度的可迭代对象中分解元素
核心点: 当可迭代对象的长度超过所需求分解的元素个数时,*表达式就可以派上用场。
举个最简单的例子:
s = 'HelloWorld'
a,b,*c,d = s
print(a) #'H'
print(c) #['l', 'l', 'o', 'W', 'o', 'r', 'l']
在指定待丢弃值的变量名时,可以通过增加 ’ '来实现同时丢弃多个值:
record = ['ACME',50,123.45,(12,18,2012)]
name, *_ , (*_, year) = record
print(name) # 'ACME'
print(year) # 2012
1.3 保存最后N个元素
核心点: from collections import deque,而deque([maxlen=N])可以创建一个最大长度为N的队列(当记录的元素超过N时,移除最老的记录),若不指定长度,则得到一个无界限的队列,可以在两端执行添加和弹出操作。
例1:
from collections import deque
q = deque(maxlen=3)
q.append(1)
q.append(2)
q.append(3)
print(q)# deque([1,2,3],maxlen=3)
q.append(4)
print(q)# deque([2,3,4],maxlen=3)
q.appendleft(1)
print(q)#deque([3,4,1], maxlen=3)
q.popleft(3) # 3(左侧剔除并返回值3)
需要注意的是,从列表头部插入或移除元素的复杂度为O(N),而队列两端添加或弹出元素的复杂度均为O(1)。
1.4 找到最大或最小的N个元素
核心点:本章中提供了4个比较大小时的解题思路:①如果只是想找到最小或最大的元素(N=1),那么用min()和max()会更加快,;②若N与集合本身的大小差不多大,通常采用 排序-切片 结合的方式更快,即使用sorted(items)[:N]或sorted(items)[-N:];③当所要找的元素数量相对较小时,nlargest() 和 nsmallest()才最适用(它们还可以接受一个参数key):heapq.nlargest(N,list,key = lambda s: s[’ ']);④ N远小于集合中元素的总数目,那么先将数据转化为列表,再使用heapq.heapify()以线性时间将列表转化为堆,heap[0]总是最小的元素,而heapq.heappop()会将第一个元素(最小的)弹出。
例1:
import heapq
data = [
{'name':'Tom','age':18,'salary':4000},
{'name':'Jimmy','age':25,'salary':10000},
{'name':'Jane','age':33,'salary':40000},
{'name':'Ferrando','age':30,'salary':32000},
]
print(heapq.nlargest(2,data, key = lambda s: s['salary']))
#[{'name': 'Jane', 'age': 33, 'salary': 40000}, {'name': 'Ferrando', 'age': 30, 'salary': 32000}]
例2:
import heapq
data = [1,3,5,9,7,4,6,12,-1,-13]
heap = list(data)
heapq.heapify(heap)
print(heap[0]) # -13
print(heapq.heappop(heap))# -13
print(heapq.heappop(heap))# -1
1.5 实现优先级队列
核心点:heapq.heappush(堆,value)以给定的优先级来对元素排序。
让我们通过书本上的案例来解析相关操作:
例1:
import heapq
class PriorityQueue: #定义一个类
def __init__(self):
self._queue = [] #私有变量
self._index = 0
def push(self,item,priority):
heaqp.heappush(self._queue,(-priority, self._index, item))#value的部分以元组形式存在
self._index += 1 #引入额外的索引值,当优先级相同时也可以根据输入的先后顺序判断应该优先返回哪个元素
def pop(self):
return heapq.heappop(self._queue)[-1]
class Item:
def __init__(self,name):
self.name = name
def __repr__(self):
return 'Item({!r})'.format(self.name) # !r 只能与format连用,表示返回对象本体。
#__repr__方法返回的是字符串或者一些有用的描述。
q = PriroityQueue()
q.push(Item('foo'),1)
q.push(Item('bar'),5)
q.push(Item('spam'),4)
q.push(Item('grok'),1)
print(q.pop()) # Item('bar')#最先输出优先级最高的是因为priority取的是负值。
print(q.pop())# Item('spam')
print(q.pop())# Item('foo')#优先级与grok相同,但比grok先加入队列,因此先弹出
print(q.pop())# Item('grok')
至于书本中关于实例和元组对比的说明我就不再赘述。
另附上几个heapq模块中的方法:
heapq.heapreplace(heap,item):加入item元素,并返回heap中最小元素;
heapq.heappushpop(heap,item):加入item元素,并返回heap中最小元素
heapq._heappop_max(heap):返回heap中最大值
heapq.merge(多个有序序列,key:None,reverse:False):合并有序序列再进行迭代,heapq.merge()的迭代性质意味着它对所有提供的序列都不会做一次性读取。这意味着可以利用它处理非常长的序列,而开销却非常小。
1.6 在字典中将键映射到多个值上:
核心点:创建一键多值字典可以利用列表或集合保存这些值:列表可以保持输入顺序,集合可以消除重复元素(但顺序会被打乱)。
方法:①from collections import defaultdict , defaultdict 会自动初始化第一个值,只需关注添加元素:
例1:
d= defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['a'].append(4)
print(d) # defaultdict(, {'a': [1, 2, 4]})
②创建普通字典,再调用setdefault:
例2:
d = {}
d.setdefault('a',[]).append(1)
d.setdefault('a',[]).append(2)
d.setdefault('b',[]).append(3)
print(d) # {'a': [1, 2], 'b': [3]}
此种方法下,每次调用setdefault 都会创造一个初始值的新实例–空列表[]。
例3:
data = [('p',1),('p',2),('p',3)]
d = defaultdict(list)
for key,value in data:
d[key].append(value)
print(d)#defaultdict(, {'p': [1, 2, 3]})
1.7 让字典保持有序
核心点: from collections import OrderedDict,对字典进行迭代时,它会严格按照元素初始添加的顺序进行,但缺点也比较明显,那就是由于OrderedDict内部维护了一个双向链表,所以它的大小是普通字典的2倍多,因此使用前需要衡量利弊。
例1
from collections import OrderedDict
d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4
for key in d:
print(key,d[key])#foo 1 bar 2 spam 3 grok 4
1.8 与字典有关的计算问题
核心点:若要求字典内数据的最大值最小值,通常可以利用zip()将字典的键和值反转过来即可求得,若再需要对数据进行排序则可以配合sorted()。
例1
prices = {
'ACME': 45.23,
'AAPL':612.78,
'IBM':205.55,
'HPQ':37.20,
'FB':10.75
}
min_price = min(zip(prices.values(),prices.keys())) # min_price is{10.75,'FB'}
max_price = max(zip(prices.values(),prices.keys()))#max_price is {612.78,'AAPL'}
price_sorted = sorted(zip(prices.values,prices.keys()))
#price_sorted is [(10.75, 'FB'),..., (612.78, 'AAPL')]
*需要注意的是:zip()创建的是一个迭代器,内容只能被消费一次。
若不使用zip(),则需要至少两步才能获得答案:
题如上:
min_name = min(prices, lambda k : prices[k])#min_price 输出为'FB'
print(min_name, prices[min_name])#'FB',10.75
1.9 在两个字典中寻找相同点
核心点:字典的键(包括keys(),items()方法)支持常见的集合操作,如求并集、交集和差集。但由于字典的值并不一定唯一,因此并不直接支持集合操作(可以转为集合后再进行操作)。
例1:
a = {
'x': 1,
'y': 2,
'z': 3
}
b = {
'x':11,
'y': 2,
'w': 10
}
print(a.keys() & b.keys()) #{'x','y'}
print(a.keys() - b.keys()) #{'z'}
print(a.keys()|b.keys())#{'w', 'z', 'y', 'x'}
print(a.items() & b.items())# {('y',2)}
这些类型的操作也可以用来修改或过滤掉字典中的内容。
如:
c = {key:a[key] for key in a.keys() -{'z','w'}}
print(c) # {'x':1,'y':2}
1.10 从序列中移除重复项并保持元素间顺序不变
核心点:①序列中的值可哈希,则可以通过使用集合和生成器解决:
例1:
def dedupe(items):
seen = set()
for item in items:
if item not in seen:#若元素不在seen这个集合中,则继续以下操作
yield item
seen.add(item)
a = [1,5,2,1,9,1,5,10]
b = list(dedupe(a))
print(b)#[1, 5, 2, 9, 10]
*return和yield的区别:return 在返回结果后结束函数的运行,而yield 则是让函数变成一个生成器,生成器每次产生一个值(yield语句),函数被冻结,被唤醒后再产生一个值,因此yield相较return更为迅速并节省空间。
② 若想在不可哈希的对象(比如列表中)序列中取出重复项:
例2:
def dedupe(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)
a = [{'x':1,'y':2},{'x':1,'y':3},{'x':1,'y':2},{'x':2,'y':4}]
print(list(dedupe(a, key = lambda d: (d['x'],d['y']))))
#[{'x':1,'y':2},{'x':1,'y':3},{'x':2,'y':4}]
print(list(dedupe(a, key = lambda d: d['x'])))
#[{'x':1,'y':2},{'x':2,'y':4}]
本例中,我们只根据对象的某个字段或属性来去除重复项,通过匿名函数获得可哈希的类型。
1.11 对切片命名
核心点:代码中如果有很多硬编码的索引值,那么其可读性和可维护性必定会下降,使用内置slice()函数创建切片对象,可以用在任何允许进行切片操作的地方:
例1:
items = [0,1,2,3,4,5,6]
a = slice(2,4)
print(items[2:4]) #[2,3]
print(items[a])#[2,3]
slice切片与indices(size)方法相结合可以防止出现索引操作出现IndexError错误,如:
print(a.indices(3)) # (2,3,1)
print(a.indices(100))#(2,3,1)
此处的size相当于slice(start,end,step)的end,
new_end = min(end,size)