Python 列表推導及優先級隊列的實現

這一篇是《流暢的 python》讀書筆記。主要介紹列表、列表推導有關的話題,最後演示如何用列表實現一個優先級隊列。html

Python 內置序列類型

Python 標準庫用 C 實現了豐富的序列類型:python

容器序列:

list、tuple和 collections.deque 這些序列能存放不一樣類型的數據。算法

扁平序列:

str、bytes、bytearray、memoryview 和 array.array,這類序列只能容納一種類型。api

容器序列存放的是它們所包含的任意類型的對象的引用,而扁平序列裏存放的是值而不是引用(也能夠說扁平序列其實存放的是一段連續的內存空間)。app

若是按序列是否可被修改來分類,序列分爲可變序列不可變序列:函數

可變序列

list、bytearray、array.array、collections.deque 和 memoryview。ui

不可變序列

tuple、str和 bytes。spa

下圖顯示了可變序列(MutableSequence)和不可變序列(sequence)的差別:3d

可變序列(MutableSequence)和不可變序列(sequence)的差別
可變序列(MutableSequence)和不可變序列(sequence)的差別

從這個圖能夠看出,可變序列從不可變序列那裏繼承了一些方法。code

列表推導和生成器表達式

列表(list)是 Python 中最基礎的序列類型。list 是一個可變序列,而且能同時存放不一樣類型的元素。
列表的基礎用法這裏就再也不介紹了,這裏主要介紹一下列表推導。

列表推導和可讀性

列表推導是構建列表的快捷方式,而且有更好的可讀性。
先看下面兩段代碼:

#1. 把一個字符串變成 unicode 碼位的列表

>>> symbols = '$&@#%^&*'
>>> codes = []
>>> for symbol in symbols:
        codes.append(ord(symbol))

>>> codes
[36, 38, 64, 35, 37, 94, 38, 42]複製代碼

#2. 把一個字符串變成 unicode 碼位的列表 使用列表推導

>>> symbols = '$&@#%^&*'
>>> codes = [ord(s) for s in symbols]
>>> codes
[36, 38, 64, 35, 37, 94, 38, 42]複製代碼

對比發現,若是理解列表推導的話,第二段代碼比第一段更簡潔可讀性也更好。
固然,列表推導也不該該被濫用,一般的原則是只用列表推導來建立新的列表,而且儘可能保持簡短。
若是列表推導超過兩行,就應該考慮要不要使用 for 循環重寫了。

NOTE

在 Python2 中列表推導有變量泄露的問題

#Python2 的例子

>>> x = 'my precious'
>>> dummy = [x for x in 'ABC']
>>> x
'C'複製代碼

這裏 x 原來的值被取代了,變成了列表推導中的最後一個值,須要避免這個問題。好消息是 Python3解決了這個問題。

#Python3 的例子

>>> x = 'ABC'
>>> dummy = [ord(x) for x in x]
>>> x 
'ABC'
>>> dummy
[65, 66, 67]複製代碼

能夠看到,這裏 x 原有的值被保留了,列表推導也建立了正確的列表。

笛卡爾積

列表推導還能夠生成兩個或以上的可迭代類型的笛卡爾積。

笛卡爾積是一個列表,列表裏的元素是由輸入的可迭代類型的元素對構成的元組,所以笛卡爾積列表的長度等於輸入變量的長度的成績,如圖所示:

笛卡爾積
笛卡爾積

# 使用列表推導計算笛卡爾積代碼以下

>>> suits = ['spades', 'diamonds', 'clubs', 'hearts']
>>> nums = ['A', 'K', 'Q']
>>> cards = [(num, suit) for num in nums for suit in suits]
>>> cards
[('A', 'spades'),
 ('A', 'diamonds'),
 ('A', 'clubs'),
 ('A', 'hearts'),
 ('K', 'spades'),
 ('K', 'diamonds'),
 ('K', 'clubs'),
 ('K', 'hearts'),
 ('Q', 'spades'),
 ('Q', 'diamonds'),
 ('Q', 'clubs'),
 ('Q', 'hearts')]複製代碼

這裏獲得的結果是先按數字排列,再按圖案排列。若是想先按圖案排列再按數字排列,只須要調整 for 從句的前後順序。

過濾序列元素

問題:你有一個數據序列,想利用一些規則從中提取出須要的值或者是縮短序列

最簡單的過濾序列元素的方法是使用列表推導。好比:

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> [n for n in mylist if n >0]
[1, 4, 10, 2, 3]複製代碼

使用列表推導的一個潛在缺陷就是若干輸入很是大的時候會產生一個很是大的結果集,佔用大量內存。這個時候,使用生成器表達式迭代產生過濾元素是一個好的選擇。

生成器表達式

生成器表達式遵照了迭代器協議,能夠逐個產出元素,而不是先創建一個完整的列表,而後再把這個列表傳遞到某個構造函數裏。

生成器表達式的語法跟列表推導差很少,只須要把方括號換成圓括號。

# 使用生成器表達式建立列表

>>> pos = (n for n in mylist if n > 0)
>>> pos
<generator object <genexpr> at 0x1006a0eb0>
>>> for x in pos:
... print(x) 
...
1
4
10 
2 
3複製代碼

若是生成器表達式是一個函數調用過程當中惟一的參數,那麼不須要額外再用括號把它圍起來。例如:

tuple(n for n in mylist)複製代碼

若是生成器表達式是一個函數調用過程當中其中一個參數,此時括號是必須的。好比:

>>> import array
>>> array.array('list', (n for n in mylist))
array('list', [1, 4, 10, 2, 3])複製代碼

實現一個優先級隊列

問題

怎麼實現一個按優先級排序的隊列?並在這個隊列上每次 pop 操做老是返回優先級最高的那個元素

解決方法

利用 heapq 模塊

heapq 是 python 的內置模塊,源碼位於 Lib/heapq.py ,該模塊提供了基於堆的優先排序算法。

堆的邏輯結構就是徹底二叉樹,而且二叉樹中父節點的值小於等於該節點的全部子節點的值。這種實現可使用 heap[k] <= heap[2k+1] 而且 heap[k] <= heap[2k+2] (其中 k 爲索引,從 0 開始計數)的形式體現,對於堆來講,最小元素即爲根元素 heap[0]。

能夠經過 list 對 heap 進行初始化,或者經過 api 中的 heapify 將已知的 list 轉化爲 heap 對象。

heapq 提供的一些方法以下:

  • heap = [] #建立了一個空堆
  • heapq.heappush(heap, item):向 heap 中插入一個元素
  • heapq.heappop(heap):返回 root 節點,即 heap 中最小的元素
  • heapq.heappushpop(heap, item):向 heap 中加入 item 元素,並返回 heap 中最小元素
  • heapq.heapify(x)
  • heapq.nlargest(n, iterable, key=None):返回可枚舉對象中的 n 個最大值,並返回一個結果集 list,key 爲對該結果集的操做
  • heapq.nsmallest(n, iterable, key=None):同上相反

實現以下:

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]複製代碼

下面是它的使用方法:

>>> class Item:
        def __init__(self, name):
            self.name = name
        def __repr__(self):
            return 'Item({!r})'.format(self.name)

>>> q = PriorityQueue()
>>> q.push(Item('foo'), 1)
>>> q.push(Item('bar'), 5)
>>> q.push(Item('spam'), 4)
>>> q.push(Item('grok'), 1)
>>> q.pop()
Item('bar') 
>>> q.pop() 
Item('spam') 
>>> q.pop() 
Item('foo') 
>>> q.pop() 
Item('grok')複製代碼

經過執行結果咱們能夠發現,第一個 pop() 操做返回優先級最高的元素。兩個優先級相同的元素(foo 和 grok),pop 操做按照它們被插入到隊列的順序返回。

函數 heapq.heappush() 和 heapq.heappop() 分別在隊列 queue 上插入和刪除第一個元素,而且隊列 queue 保證 第一個元素擁有最小優先級。 heappop() 函數老是返回 最小的 的元素,這就是保證隊列 pop 操做返回正確元素的關鍵。另外,因爲 push 和 pop 操做時間複雜度爲 O(log N),其中 N 是堆的大小,所以就算是 N 很大的時候它們 運行速度也依舊很快。
在上面代碼中,隊列包含了一個 (-priority, index, item) 的元組。優先級爲負 數的目的是使得元素按照優先級從高到低排序。這個跟普通的按優先級從低到高排序的堆排序恰巧相反。
index 變量的做用是保證同等優先級元素的正確排序。經過保存一個不斷增長的 index 下標變量,能夠確保元素按照它們插入的順序排序。並且, index 變量也在相 同優先級元素比較的時候起到重要做用。

實現上邊排序的關鍵是 元組是支持比較的:

>>> a = (1, Item('foo')) 
>>> b = (5, Item('bar')) 
>>> a < b
True
>>> c = (1, Item('grok'))
>>> a < c
Traceback (most recent call last):
File "<stdin>", line 1, in <module> 
TypeError: unorderable types: Item() < Item()複製代碼

當第一個值大小相等時,因爲Item 並不支持比較會拋出 TypeError。爲了不上述錯誤,咱們引入了index(不可能用兩個元素有相同的 index 值), 變量組成了(priority, index, item) 三元組。如今再比較就不會出現上述問題了:

>>> a = (1, 0, Item('foo')) 
>>> b = (5, 1, Item('bar')) 
>>> c = (1, 2, Item('grok')) 
>>> a < b
True
>>> a < c 
True複製代碼

主要介紹列表、列表推導有關的話題,最後演示如何用heapq列表實現一個優先級隊列。下一篇介紹元組

參考連接


最後,感謝女友支持。

歡迎關注(April_Louisa) 請我喝芬達
歡迎關注
歡迎關注
請我喝芬達
請我喝芬達
相關文章
相關標籤/搜索