Python中的高级数据结构详解
這篇文章主要介紹了Python中的高級數據結構詳解,本文講解了Collection、Array、Heapq、Bisect、Weakref、Copy以及Pprint這些數據結構的用法,需要的朋友可以參考下
數據結構
數據結構的概念很好理解,就是用來將數據組織在一起的結構。換句話說,數據結構是用來存儲一系列關聯數據的東西。在Python中有四種內建的數據結構,分別是List、Tuple、Dictionary以及Set。大部分的應用程序不需要其他類型的數據結構,但若是真需要也有很多高級數據結構可供選擇,例如Collection、Array、Heapq、Bisect、Weakref、Copy以及Pprint。本文將介紹這些數據結構的用法,看看它們是如何幫助我們的應用程序的。
關于四種內建數據結構的使用方法很簡單,并且網上有很多參考資料,因此本文將不會討論它們。
1. Collections
collections模塊包含了內建類型之外的一些有用的工具,例如Counter、defaultdict、OrderedDict、deque以及nametuple。其中Counter、deque以及defaultdict是最常用的類。
1.1 Counter()
如果你想統計一個單詞在給定的序列中一共出現了多少次,諸如此類的操作就可以用到Counter。來看看如何統計一個list中出現的item次數:
from collections import Counterli = ["Dog", "Cat", "Mouse", 42, "Dog", 42, "Cat", "Dog"] a = Counter(li) print a # Counter({'Dog': 3, 42: 2, 'Cat': 2, 'Mouse': 1})若要統計一個list中不同單詞的數目,可以這么用:
from collections import Counterli = ["Dog", "Cat", "Mouse", 42, "Dog", 42, "Cat", "Dog"] a = Counter(li) print a # Counter({'Dog': 3, 42: 2, 'Cat': 2, 'Mouse': 1})print len(set(li)) # 4如果需要對結果進行分組,可以這么做:
''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' from collections import Counterli = ["Dog", "Cat", "Mouse","Dog","Cat", "Dog"] a = Counter(li)print a # Counter({'Dog': 3, 'Cat': 2, 'Mouse': 1})print "{0} : {1}".format(a.values(),a.keys()) # [1, 3, 2] : ['Mouse', 'Dog', 'Cat']print(a.most_common(3)) # [('Dog', 3), ('Cat', 2), ('Mouse', 1)]以下的代碼片段找出一個字符串中出現頻率最高的單詞,并打印其出現次數。
import re from collections import Counterstring = """ Lorem ipsum dolor sit amet, consecteturadipiscing elit. Nunc ut elit id mi ultriciesadipiscing. Nulla facilisi. Praesent pulvinar,sapien vel feugiat vestibulum, nulla dui pretium orci,non ultricies elit lacus quis ante. Lorem ipsum dolorsit amet, consectetur adipiscing elit. Aliquampretium ullamcorper urna quis iaculis. Etiam ac massased turpis tempor luctus. Curabitur sed nibh eu elitmollis congue. Praesent ipsum diam, consectetur vitaeornare a, aliquam a nunc. In id magna pellentesquetellus posuere adipiscing. Sed non mi metus, at laciniaaugue. Sed magna nisi, ornare in mollis in, mollissed nunc. Etiam at justo in leo congue mollis.Nullam in neque eget metus hendrerit scelerisqueeu non enim. Ut malesuada lacus eu nulla bibendumid euismod urna sodales. """words = re.findall(r'\w ', string) #This finds words in the documentlower_words = [word.lower() for word in words] #lower all the wordsword_counts = Counter(lower_words) #counts the number each time a word appears print word_counts# Counter({'elit': 5, 'sed': 5, 'in': 5, 'adipiscing': 4, 'mollis': 4, 'eu': 3, # 'id': 3, 'nunc': 3, 'consectetur': 3, 'non': 3, 'ipsum': 3, 'nulla': 3, 'pretium': # 2, 'lacus': 2, 'ornare': 2, 'at': 2, 'praesent': 2, 'quis': 2, 'sit': 2, 'congue': 2, 'amet': 2, # 'etiam': 2, 'urna': 2, 'a': 2, 'magna': 2, 'lorem': 2, 'aliquam': 2, 'ut': 2, 'ultricies': 2, 'mi': 2, # 'dolor': 2, 'metus': 2, 'ac': 1, 'bibendum': 1, 'posuere': 1, 'enim': 1, 'ante': 1, 'sodales': 1, 'tellus': 1, # 'vitae': 1, 'dui': 1, 'diam': 1, 'pellentesque': 1, 'massa': 1, 'vel': 1, 'nullam': 1, 'feugiat': 1, 'luctus': 1, # 'pulvinar': 1, 'iaculis': 1, 'hendrerit': 1, 'orci': 1, 'turpis': 1, 'nibh': 1, 'scelerisque': 1, 'ullamcorper': 1, # 'eget': 1, 'neque': 1, 'euismod': 1, 'curabitur': 1, 'leo': 1, 'sapien': 1, 'facilisi': 1, 'vestibulum': 1, 'nisi': 1, # 'justo': 1, 'augue': 1, 'tempor': 1, 'lacinia': 1, 'malesuada': 1})1.2 Deque
Deque是一種由隊列結構擴展而來的雙端隊列(double-ended queue),隊列元素能夠在隊列兩端添加或刪除。因此它還被稱為頭尾連接列表(head-tail linked list),盡管叫這個名字的還有另一個特殊的數據結構實現。
Deque支持線程安全的,經過優化的append和pop操作,在隊列兩端的相關操作都能夠達到近乎O(1)的時間復雜度。雖然list也支持類似的操作,但是它是對定長列表的操作表現很不錯,而當遇到pop(0)和insert(0, v)這樣既改變了列表的長度又改變其元素位置的操作時,其復雜度就變為O(n)了。
來看看相關的比較結果:
''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' import time from collections import dequenum = 100000def append(c):for i in range(num):c.append(i)def appendleft(c):if isinstance(c, deque):for i in range(num):c.appendleft(i)else:for i in range(num):c.insert(0, i) def pop(c):for i in range(num):c.pop()def popleft(c):if isinstance(c, deque):for i in range(num):c.popleft()else:for i in range(num):c.pop(0)for container in [deque, list]:for operation in [append, appendleft, pop, popleft]:c = container(range(num))start = time.time()operation(c)elapsed = time.time() - startprint "Completed {0}/{1} in {2} seconds: {3} ops/sec".format(container.__name__, operation.__name__, elapsed, num / elapsed)# Completed deque/append in 0.0250000953674 seconds: 3999984.74127 ops/sec # Completed deque/appendleft in 0.0199999809265 seconds: 5000004.76838 ops/sec # Completed deque/pop in 0.0209999084473 seconds: 4761925.52225 ops/sec # Completed deque/popleft in 0.0199999809265 seconds: 5000004.76838 ops/sec # Completed list/append in 0.0220000743866 seconds: 4545439.17637 ops/sec # Completed list/appendleft in 21.3209998608 seconds: 4690.21155917 ops/sec # Completed list/pop in 0.0240001678467 seconds: 4166637.52682 ops/sec # Completed list/popleft in 4.01799988747 seconds: 24888.0046791 ops/sec另一個例子是執行基本的隊列操作:
代碼如下:
from collections import deque q = deque(range(5)) q.append(5) q.appendleft(6) print q print q.pop() print q.popleft() print q.rotate(3) print q print q.rotate(-1) print q# deque([6, 0, 1, 2, 3, 4, 5]) # 5 # 6 # None # deque([2, 3, 4, 0, 1]) # None # deque([3, 4, 0, 1, 2])譯者注:rotate是隊列的旋轉操作,Right rotate(正參數)是將右端的元素移動到左端,而Left rotate(負參數)則相反。
1.3 Defaultdict
這個類型除了在處理不存在的鍵的操作之外與普通的字典完全相同。當查找一個不存在的鍵操作發生時,它的default_factory會被調用,提供一個默認的值,并且將這對鍵值存儲下來。其他的參數同普通的字典方法dict()一致,一個defaultdict的實例同內建dict一樣擁有同樣地操作。
defaultdict對象在當你希望使用它存放追蹤數據的時候很有用。舉個例子,假定你希望追蹤一個單詞在字符串中的位置,那么你可以這么做:
代碼如下:
from collections import defaultdicts = "the quick brown fox jumps over the lazy dog"words = s.split() location = defaultdict(list) for m, n in enumerate(words):location[n].append(m)print location# defaultdict(, {'brown': [2], 'lazy': [7], 'over': [5], 'fox': [3], # 'dog': [8], 'quick': [1], 'the': [0, 6], 'jumps': [4]})是選擇lists或sets與defaultdict搭配取決于你的目的,使用list能夠保存你插入元素的順序,而使用set則不關心元素插入順序,它會幫助消除重復元素。
代碼如下:
''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' from collections import defaultdicts = "the quick brown fox jumps over the lazy dog"words = s.split() location = defaultdict(set) for m, n in enumerate(words):location[n].add(m)print location# defaultdict(, {'brown': set([2]), 'lazy': set([7]), # 'over': set([5]), 'fox': set([3]), 'dog': set([8]), 'quick': set([1]), # 'the': set([0, 6]), 'jumps': set([4])})另一種創建multidict的方法:
s = "the quick brown fox jumps over the lazy dog" d = {} words = s.split()for key, value in enumerate(words):d.setdefault(key, []).append(value) print d# {0: ['the'], 1: ['quick'], 2: ['brown'], 3: ['fox'], 4: ['jumps'], 5: ['over'], 6: ['the'], 7: ['lazy'], 8: ['dog']}一個更復雜的例子:
class Example(dict):def __getitem__(self, item):try:return dict.__getitem__(self, item)except KeyError:value = self[item] = type(self)()return valuea = Example()a[1][2][3] = 4 a[1][3][3] = 5 a[1][2]['test'] = 6print a # {1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}2. Array
array模塊定義了一個很像list的新對象類型,不同之處在于它限定了這個類型只能裝一種類型的元素。array元素的類型是在創建并使用的時候確定的。
如果你的程序需要優化內存的使用,并且你確定你希望在list中存儲的數據都是同樣類型的,那么使用array模塊很合適。舉個例子,如果需要存儲一千萬個整數,如果用list,那么你至少需要160MB的存儲空間,然而如果使用array,你只需要40MB。但雖然說能夠節省空間,array上幾乎沒有什么基本操作能夠比在list上更快。
在使用array進行計算的時候,需要特別注意那些創建list的操作。
例如,使用列表推導式(list comprehension)的時候,會將array整個轉換為list,使得存儲空間膨脹。一個可行的替代方案是使用生成器表達式創建新的array。
看代碼:
import arraya = array.array("i", [1,2,3,4,5]) b = array.array(a.typecode, (2*x for x in a))因為使用array是為了節省空間,所以更傾向于使用in-place操作。
一種更高效的方法是使用enumerate:
import arraya = array.array("i", [1,2,3,4,5]) for i, x in enumerate(a):a[i] = 2*x對于較大的array,這種in-place修改能夠比用生成器創建一個新的array至少提升15%的速度。
那么什么時候使用array呢?是當你在考慮計算的因素之外,還需要得到一個像C語言里一樣統一元素類型的數組時。
代碼如下:
''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' import array from timeit import Timerdef arraytest():a = array.array("i", [1, 2, 3, 4, 5])b = array.array(a.typecode, (2 * x for x in a))def enumeratetest():a = array.array("i", [1, 2, 3, 4, 5])for i, x in enumerate(a):a[i] = 2 * xif __name__=='__main__':m = Timer("arraytest()", "from __main__ import arraytest")n = Timer("enumeratetest()", "from __main__ import enumeratetest")print m.timeit() # 5.22479210582print n.timeit() # 4.343671967173.Heapq
heapq模塊使用一個用堆實現的優先級隊列。堆是一種簡單的有序列表,并且置入了堆的相關規則。
堆是一種樹形的數據結構,樹上的子節點與父節點之間存在順序關系。二叉堆(binary heap)能夠用一個經過組織的列表或數組結構來標識,在這種結構中,元素N的子節點的序號為2*N 1和2*N 2(下標始于0)。簡單來說,這個模塊中的所有函數都假設序列是有序的,所以序列中的第一個元素(seq[0])是最小的,序列的其他部分構成一個二叉樹,并且seq[i]節點的子節點分別為seq[2*i 1]以及seq[2*i 2]。當對序列進行修改時,相關函數總是確保子節點大于等于父節點。
代碼如下:
import heapqheap = []for value in [20, 10, 30, 50, 40]:heapq.heappush(heap, value)while heap:print heapq.heappop(heap)heapq模塊有兩個函數nlargest()和nsmallest(),顧名思義,讓我們來看看它們的用法。
代碼如下:
import heapqnums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2] print(heapq.nlargest(3, nums)) # Prints [42, 37, 23] print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]兩個函數也能夠通過一個鍵參數使用更為復雜的數據結構,例如:
''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' import heapqportfolio = [ {'name': 'IBM', 'shares': 100, 'price': 91.1}, {'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'HPQ', 'shares': 35, 'price': 31.75}, {'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'ACME', 'shares': 75, 'price': 115.65} ] cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price']) expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])print cheap# [{'price': 16.35, 'name': 'YHOO', 'shares': 45}, # {'price': 21.09, 'name': 'FB', 'shares': 200}, {'price': 31.75, 'name': 'HPQ', 'shares': 35}]print expensive# [{'price': 543.22, 'name': 'AAPL', 'shares': 50}, {'price': 115.65, 'name': 'ACME', # 'shares': 75}, {'price': 91.1, 'name': 'IBM', 'shares': 100}]來看看如何實現一個根據給定優先級進行排序,并且每次pop操作都返回優先級最高的元素的隊列例子。
代碼如下:
import heapqclass Item:def __init__(self, name):self.name = namedef __repr__(self):return 'Item({!r})'.format(self.name)class PriorityQueue:def __init__(self):self._queue = []self._index = 0def push(self, item, priority):heapq.heappush(self._queue, (-priority, self._index, item))self._index = 1def pop(self):return heapq.heappop(self._queue)[-1]q = PriorityQueue() 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') print q.pop() # Item('spam') print q.pop() # Item('foo') print q.pop() # Item('grok')4. Bisect
bisect模塊能夠提供保持list元素序列的支持。它使用了二分法完成大部分的工作。它在向一個list插入元素的同時維持list是有序的。在某些情況下,這比重復的對一個list進行排序更為高效,并且對于一個較大的list來說,對每步操作維持其有序也比對其排序要高效。
假設你有一個range集合:
a = [(0, 100), (150, 220), (500, 1000)]如果我想添加一個range (250, 400),我可能會這么做:
import bisecta = [(0, 100), (150, 220), (500, 1000)]bisect.insort_right(a, (250,400))print a # [(0, 100), (150, 220), (250, 400), (500, 1000)]我們可以使用bisect()函數來尋找插入點:
''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' import bisecta = [(0, 100), (150, 220), (500, 1000)]bisect.insort_right(a, (250,400)) bisect.insort_right(a, (399, 450)) print a # [(0, 100), (150, 220), (250, 400), (500, 1000)]print bisect.bisect(a, (550, 1200)) # 5bisect(sequence, item) => index 返回元素應該的插入點,但序列并不被修改。
代碼如下:
import bisecta = [(0, 100), (150, 220), (500, 1000)]bisect.insort_right(a, (250,400)) bisect.insort_right(a, (399, 450)) print a # [(0, 100), (150, 220), (250, 400), (500, 1000)]print bisect.bisect(a, (550, 1200)) # 5 bisect.insort_right(a, (550, 1200)) print a # [(0, 100), (150, 220), (250, 400), (399, 450), (500, 1000), (550, 1200)]新元素被插入到第5的位置。
5. Weakref
weakref模塊能夠幫助我們創建Python引用,卻不會阻止對象的銷毀操作。這一節包含了weak reference的基本用法,并且引入一個代理類。
在開始之前,我們需要明白什么是strong reference。strong reference是一個對對象的引用次數、生命周期以及銷毀時機產生影響的指針。strong reference如你所見,就是當你將一個對象賦值給一個變量的時候產生的:
>>> a = [1,2,3] >>> b = a在這種情況下,這個列表有兩個strong reference,分別是a和b。在這兩個引用都被釋放之前,這個list不會被銷毀。
代碼如下:
class Foo(object):def __init__(self):self.obj = Noneprint 'created'def __del__(self):print 'destroyed'def show(self):print self.objdef store(self, obj):self.obj = obja = Foo() # created b = a del a del b # destroyedWeak reference則是對對象的引用計數器不會產生影響。當一個對象存在weak reference時,并不會影響對象的撤銷。這就說,如果一個對象僅剩下weak reference,那么它將會被銷毀。
你可以使用weakref.ref函數來創建對象的weak reference。這個函數調用需要將一個strong reference作為第一個參數傳給函數,并且返回一個weak reference。
代碼如下:
''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:531509025 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' >>> import weakref >>> a = Foo() created >>> b = weakref.ref(a) >>> b一個臨時的strong reference可以從weak reference中創建,即是下例中的b():
>>> a == b() True >>> b().show() None請注意當我們刪除strong reference的時候,對象將立即被銷毀。
代碼如下:
>>> del a destroyed如果試圖在對象被摧毀之后通過weak reference使用對象,則會返回None:
代碼如下:
>>> b() is None True若是使用weakref.proxy,就能提供相對于weakref.ref更透明的可選操作。同樣是使用一個strong reference作為第一個參數并且返回一個weak reference,proxy更像是一個strong reference,但當對象不存在時會拋出異常。
總結
以上是生活随笔為你收集整理的Python中的高级数据结构详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 开发工具链全解
- 下一篇: python执行系统命令后获取返回值的几