1. 程式人生 > >Python學習筆記(七)—— List.sort 與二進位制搜尋bisect

Python學習筆記(七)—— List.sort 與二進位制搜尋bisect

程式碼及內容源自《Fluent Python》——Luciano Ramalho 著

List.sort方法會直接進行排序操作,過程中既不會複製原list,也不會生成新的list物件。
與之相反,sorted()函式則會生成並返回一個新的list。

>>> fruits=['grape','raspberry','apple','banana']
>>> sorted(fruits)
['apple', 'banana', 'grape', 'raspberry']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits, reverse=True)
['raspberry', 'grape', 'banana', 'apple']
>>> sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> fruits.sort()
>>> fruits
['apple', 'banana', 'grape', 'raspberry']

序列經過排序後,可以提高搜尋的效率。幸運的是,Python標準庫中已經提供了標準二進位制搜尋演算法 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]

ROW_FMT = '{0:2d} @ {1:2d}    {2}{0:2d}'
def demo(bisect_fn): for needle in reversed(NEEDLES): position = bisect_fn(HAYSTACK, needle) offset = position * ' |' print(ROW_FMT.format(needle, position, offset)) if __name__ == '__main__': if sys.argv[-1] == 'left': bisect_fn = bisect.bisect_left else: bisect_fn = bisect.bisect print('DEMO:', bisect_fn.__name__) print('haysrtack ->',' '.join('%2d' % n for n in HAYSTACK)) demo(bisect_fn)
DEMO: bisect
haysrtack ->  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




DEMO: bisect_left
haysrtack ->  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

bisect實際上是bisect_right的別名,與之相對應的另一個函式是bisect_left。二者的區別在於:bisect_right的插入點在目標位置之後,而bisect_left的插入點在目標位置之前。
bisect的一個有趣的應用是根據數值進行表格的查詢,如下例中將測試成績轉換為字母表示的等級。

def grade(score, breakpoints=[60,70,80,90], grades='FDCBA'):
    i = bisect.bisect(breakpoints,score)
    return grades[i]
[grade(score) for score in [33,99,77,70,89,90,100]]
['F', 'A', 'C', 'C', 'B', 'A', 'A']

給序列排序是一件耗費資源的工作,因此一旦你已經得到了一個排序好的序列,就不要輕易破壞它。雖然,可以利用bisect(haystack, needle)來獲得位置的index,再利用haystack.insert(index,needle)實現不改變排序的插入操作;但是,利用bisect.insort更方便而且更快捷。

import bisect
import random

SIZE = 7
random.seed(1729)

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)
10 -> [10]
 0 -> [0, 10]
 6 -> [0, 6, 10]
 8 -> [0, 6, 8, 10]
 7 -> [0, 6, 7, 8, 10]
 2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]