全面深入了解python(四)
1. 用bisect来管理已排序的序列
bisect模块包含两个主要函数,bisect和insort,两个函数都利用二分查找算法在有序序列中查找或插入元素。
1.1 用bisect来搜索
bisect(haystack, needle),其中haystack必须是一个有序的序列,把needle插入位置之后,haystack还能保持升序。也就是说在这个函数返回位置前面的值,都小于或等于needle的值。
import bisect
import sys
haystack = [1,4,5,6,8,12,15,20,21,23,23,26,29,30]
needles = [0,1,2,5,8,10,22,23,29,30,31]
def demo(bisect_fn):
for needle in reversed(needles):
#用特定的bisect函数计算元素应该出现的位置
position = bisect_fn(haystack,needle)
#利用该位置来算出需要几个分隔符号
offset = position * ' |'
#打印元素和其应该出现的位置
print(f'{needle:2d} @ {position:2d} {offset}{needle:<2d}')
if __name__ == '__main__':
#选用bisect函数
bisect_fn = bisect.bisect
#打印函数抬头
print('demo:', bisect_fn.__name__)
print('haystack ->',' '.join('%2d' % n for n in haystack))
demo(bisect_fn)
运行如下:
demo: bisect
haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
31 @ 14 | | | | | | | | | | | | | |31
30 @ 14 | | | | | | | | | | | | | |30
29 @ 13 | | | | | | | | | | | | |29
23 @ 11 | | | | | | | | | | |23
22 @ 9 | | | | | | | | |22
10 @ 5 | | | | |10
8 @ 5 | | | | |8
5 @ 3 | | |5
2 @ 1 |2
1 @ 1 |1
0 @ 0 0
每一行以needle @ position(元素及其应该插入的位置)开始,然后展示了该元素在原序列中的物理位置。
bisect的表现可以从两个方面来改变:
- 首先可以用它的两个可选参数——lo和hi——来缩小搜寻的范围。lo的默认值是0,hi的默认值是序列的长度,即len()作用于该序列的返回值。
- 其次bisect函数其实是bisect_right函数的别名,其还有个bisect_left的姊妹函数。区别在于bisect_left返回的插入位置是原序列中跟被插入元素相等的元素的位置,也就是新元素会被放置于它相等的元素的前面,而bisect_right返回的则是跟它相等的元素之后的位置。这个细微的差别可能对于整个序列没有用,但是对值相等形式不同的数据类型来讲,结果就不一样了。例:1==1.0的返回值是True,1和1.0其实是两个不同的元素.
这里用bisect_left运行看看结果:
...
if __name__ == '__main__':
#选用bisect函数
bisect_fn = bisect.bisect_left
#打印函数抬头
print('demo:', bisect_fn.__name__)
print('haystack ->',' '.join('%2d' % n for n in haystack))
demo(bisect_fn)
运行如下:
demo: bisect_left
haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
31 @ 14 | | | | | | | | | | | | | |31
30 @ 13 | | | | | | | | | | | | |30
29 @ 12 | | | | | | | | | | | |29
23 @ 9 | | | | | | | | |23
22 @ 9 | | | | | | | | |22
10 @ 5 | | | | |10
8 @ 4 | | | |8
5 @ 2 | |5
2 @ 1 |2
1 @ 0 1
0 @ 0 0
如果这2个例子有些抽象,那么下面举一个简单偏实际的例子:
def grade(score,breakpoints = [60,70,80,90], grades = 'FDCBA'):
i = bisect.bisect(breakpoints,score)
return grades[i]
if __name__ == '__main__':
print([grade(score) for score in [33,99,87,62,55,80,100,90]])
这个例子建立一个用数字作为索引的查询表格,把分数和成绩对应起来。
['F', 'A', 'B', 'D', 'F', 'B', 'A', 'A']
1.2 用bisect.insort插入新元素
排序很耗时,因此在得到一个有序序列之后我们最好能够保持它的有序。
Insort(seq,item)把变量item插入到序列seq中,并保持seq的升序顺序。
import bisect
import random
SIZE = 7
random.seed(1397)
my_list = []
for i in range(SIZE):
new_item = random.randrange(SIZE*2)
bisect.insort(my_list,new_item)
print('%2d ->' % new_item,my_list)
运行如下:
1 -> [1]
5 -> [1, 5]
3 -> [1, 3, 5]
13 -> [1, 3, 5, 13]
6 -> [1, 3, 5, 6, 13]
11 -> [1, 3, 5, 6, 11, 13]
6 -> [1, 3, 5, 6, 6, 11, 13]
insort跟bisect一样,有lo和hi两个可选参数来控制查找范围,相同的也有个变体叫insort_left,这个变体在背后用的是bisect_left.
目前所提到的内容都不仅仅是对列表或元组有效,还可以应用于几乎所有的序列类型上。