1. 程式人生 > >Python 中3個常用的資料結構和演算法

Python 中3個常用的資料結構和演算法

Python內建了許多非常有用的資料結構,比如列表(list)、集合(set)以及字典(dictionary)。就絕大部分情況而言,我們可以直接使用這些資料結構。但是,通常我們還需要考慮比如搜尋、排序、排列以及篩選等這一類常見的問題。

本篇文章將介紹3種常見的資料結構和同資料有關的演算法。此外,在collections模組中也包含了針對各種資料結構的解決方案。

1. 將序列分解為單獨的變數

(1) 問題

我們有一個包含 N 個元素的元組或序列,現在想將它分解為N個單獨的變數。

(2) 解決方案

任何序列(或可迭代的物件)都可以通過一個簡單的賦值操作來分解為單獨的變數。唯一的要求是變數的總數和結構要與序列相吻合。例如:


1.  >>> p = (4, 5) 
2.  >>> x, y = p 
3.  >>> x 
4.  4 
5.  >>> y 
6.  5 
7.  >>> 
8.  >>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ] 
9.  >>> name, shares, price, date = data 
10.  >>> name 
11.  'ACME' 
12.  >>> date 
13.  (2012, 12, 21) 
14.  >>> name, shares, price, (year, mon, day) = data 
15.  >>> name 
16.  'ACME' 
17.  >>> year 
18.  2012 
19.  >>> mon 
20.  12 
21.  >>> day 
22.  21 
23.  >>> 

如果元素的數量不匹配,將得到一個錯誤提示。例如:


1.  >>> p = (4, 5) 
2.  >>> x, y, z = p 
3.  Traceback (most recent call last): 
4.  File "<stdin>", line 1, in <module> 
5.  ValueError: need more than 2 values to unpack 
6.  >>> 

(3) 討論

實際上不僅僅只是元組或列表,只要物件恰好是可迭代的,那麼就可以執行分解操作。這包括字串、檔案、迭代器以及生成器。比如:


1.  >>> s = 'Hello' 
2.  >>> a, b, c, d, e = s 
3.  >>> a 
4.  'H' 
5.  >>> b 
6.  'e' 
7.  >>> e 
8.  'o' 
9.  >>> 

當做分解操作時,有時候可能想丟棄某些特定的值。Python並沒有提供特殊的語法來實現這一點,但是通常可以選一個用不到的變數名,以此來作為要丟棄的值的名稱。例如:


1.  >>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ] 
2.  >>> _, shares, price, _ = data 
3.  >>> shares 
4.  50 
5.  >>> price 
6.  91.1 
7.  >>> 

但是請確保選擇的變數名沒有在其他地方用到過。

2. 從任意長度的可迭代物件中分解元素

(1) 問題

需要從某個可迭代物件中分解出N個元素,但是這個可迭代物件的長度可能超過N,這會導致出現“分解的值過多(too many values to unpack)”的異常。

(2) 解決方案

Python的“*表示式”可以用來解決這個問題。例如,假設開設了一門課程,並決定在期末的作業成績中去掉第一個和最後一個,只對中間剩下的成績做平均分統計。如果只有4個成績,也許可以簡單地將4個都分解出來,但是如果有24個呢?*表示式使這一切都變得簡單:


1.  def drop_first_last(grades): 
2.  first, *middle, last = grades 
3.  return avg(middle) 

另一個用例是假設有一些使用者記錄,記錄由姓名和電子郵件地址組成,後面跟著任意數量的電話號碼。則可以像這樣分解記錄:

1.  >>> record = ('Dave', '[email protected]', '773-555-1212', '847-555-1212') 
2.  >>> name, email, *phone_numbers = user_record 
3.  >>> name 
4.  'Dave' 
5.  >>> email 
6.  '[email protected]' 
7.  >>> phone_numbers 
8.  ['773-555-1212', '847-555-1212'] 
9.  >>> 

不管需要分解出多少個電話號碼(甚至沒有電話號碼),變數phone_numbers都一直是列表,而這是毫無意義的。如此一來,對於任何用到了變數phone_numbers的程式碼都不必對它可能不是一個列表的情況負責,或者額外做任何形式的型別檢查。

由*修飾的變數也可以位於列表的第一個位置。例如,比方說用一系列的值來代表公司過去8個季度的銷售額。如果想對最近一個季度的銷售額同前7個季度的平均值做比較,可以這麼做:


1.  *trailing_qtrs, current_qtr = sales_record 
2.  trailing_avg = sum(trailing_qtrs) / len(trailing_qtrs) 
3.  return avg_comparison(trailing_avg, current_qtr) 

從Python直譯器的角度來看,這個操作是這樣的:


1.  >>> *trailing, current = [10, 8, 7, 1, 9, 5, 10, 3] 
2.  >>> trailing 
3.  [10, 8, 7, 1, 9, 5, 10] 
4.  >>> current 
5.  3 

(3) 討論

對於分解未知或任意長度的可迭代物件,這種擴充套件的分解操作可謂是量身定做的工具。通常,這類可迭代物件中會有一些已知的元件或模式(例如,元素1之後的所有內容都是電話號碼),利用*表示式分解可迭代物件使得開發者能夠輕鬆利用這些模式,而不必在可迭代物件中做複雜花哨的操作才能得到相關的元素。

*式的語法在迭代一個變長的元組序列時尤其有用。例如,假設有一個帶標記的元組序列:


1.  records = [ 
2.  ('foo', 1, 2), 
3.  ('bar', 'hello'), 
4.  ('foo', 3, 4), 
5.  ] 
6.  def do_foo(x, y): 
7.  print('foo', x, y) 
8.  def do_bar(s): 
9.  print('bar', s) 
10.  for tag, *args in records: 
11.  if tag == 'foo': 
12.  do_foo(*args) 
13.  elif tag == 'bar': 
14.  do_bar(*args) 

當和某些特定的字串處理操作相結合,比如做拆分(splitting)操作時,這種*式的語法所支援的分解操作也非常有用。例如:

1.  >>> line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false' 
2.  >>> uname, *fields, homedir, sh = line.split(':') 
3.  >>> uname 
4.  'nobody' 
5.  >>> homedir 
6.  '/var/empty' 
7.  >>> sh 
8.  '/usr/bin/false' 
9.  >>> 

有時候可能想分解出某些值然後丟棄它們。在分解的時候,不能只是指定一個單獨的*,但是可以使用幾個常用來表示待丟棄值的變數名,比如_或者ign(ignored)。例如:

1.  >>> record = ('ACME', 50, 123.45, (12, 18, 2012)) 
2.  >>> name, *_, (*_, year) = record 
3.  >>> name 
4.  'ACME' 
5.  >>> year 
6.  2012 
7.  >>> 

*分解操作和各種函式式語言中的列表處理功能有著一定的相似性。例如,如果有一個列表,可以像下面這樣輕鬆將其分解為頭部和尾部:


1.  >>> items = [1, 10, 7, 4, 5, 9] 
2.  >>> head, *tail = items 
3.  >>> head 
4.  1 
5.  >>> tail 
6.  [10, 7, 4, 5, 9] 
7.  >>> 

在編寫執行這類拆分功能的函式時,人們可以假設這是為了實現某種精巧的遞迴演算法。例如:


1.  >>> def sum(items): 
2.  ... head, *tail = items 
3.  ... return head + sum(tail) if tail else head 
4.  ... 
5.  >>> sum(items) 
6.  36 
7.  >>> 

但是請注意,遞迴真的不算是Python的強項,這是因為其內在的遞迴限制所致。因此,最後一個例子在實踐中沒太大的意義,只不過是一點學術上的好奇罷了。

3. 儲存最後N個元素

(1) 問題

我們希望在迭代或是其他形式的處理過程中對最後幾項記錄做一個有限的歷史記錄統計。

(2) 解決方案

儲存有限的歷史記錄可算是collections.deque的完美應用場景了。例如,下面的程式碼對一系列文字行做簡單的文字匹配操作,當發現有匹配時就輸出當前的匹配行以及最後檢查過的N行文字。


1.  from collections import deque 
2.  def search(lines, pattern, history=5): 
3.  previous_lines = deque(maxlen=history) 
4.  for line in lines: 
5.  if pattern in line: 
6.  yield line, previous_lines 
7.  previous_lines.append(line) 
8.  # Example use on a file 
9.  if __name__ == '__main__': 
10.  with open('somefile.txt') as f: 
11.  for line, prevlines in search(f, 'python', 5): 
12.  for pline in prevlines: 
13.  print(pline, end='') 
14.  print(line, end='') 
15.  print('-'*20) 

(3) 討論

如同上面的程式碼片段中所做的一樣,當編寫搜尋某項記錄的程式碼時,通常會用到含有yield關鍵字的生成器函式。這將處理搜尋過程的程式碼和使用搜索結果的程式碼成功解耦開來。如果對生成器還不熟悉,請參見4.3節。

deque(maxlen=N)建立了一個固定長度的佇列。當有新記錄加入而佇列已滿時會自動移除最老的那條記錄。例如:


1.  >>> q = deque(maxlen=3) 
2.  >>> q.append(1) 
3.  >>> q.append(2) 
4.  >>> q.append(3) 
5.  >>> q 
6.  deque([1, 2, 3], maxlen=3) 
7.  >>> q.append(4) 
8.  >>> q 
9.  deque([2, 3, 4], maxlen=3) 
10.  >>> q.append(5) 
11.  >>> q 
12.  deque([3, 4, 5], maxlen=3) 

儘管可以在列表上手動完成這樣的操作(append、del),但佇列這種解決方案要優雅得多,執行速度也快得多。

更普遍的是,當需要一個簡單的佇列結構時,deque可祝你一臂之力。如果不指定佇列的大小,也就得到了一個無界限的佇列,可以在兩端執行新增和彈出操作,例如:


1.  >>> q = deque() 
2.  >>> q.append(1) 
3.  >>> q.append(2) 
4.  >>> q.append(3) 
5.  >>> q 
6.  deque([1, 2, 3]) 
7.  >>> q.appendleft(4) 
8.  >>> q 
9.  deque([4, 1, 2, 3]) 
10.  >>> q.pop() 
11.  3 
12.  >>> q 
13.  deque([4, 1, 2]) 
14.  >>> q.popleft() 
15.  4 

從佇列兩端新增或彈出元素的複雜度都是O(1)。這和列表不同,當從列表的頭部插入或移除元素時,列表的複雜度為O(N)。

相關推薦

Python 3常用資料結構演算法

Python內建了許多非常有用的資料結構,比如列表(list)、集合(set)以及字典(dictionary)。就絕大部分情況而言

常用資料結構演算法操作效率的對比總結

歡迎關注我新搭建的部落格:[http://www.itcodai.com/](http://www.itcodai.com/)         前面介紹了經典的資料結構和演算法,這一節我們對這些資料結構和演算法做一個總結,具體細節,請參見各個章節的詳細介紹,這裡我們用表

Python運維20常用的庫模塊

python 運維 1、psutil是一個跨平臺庫(https://github.com/giampaolo/psutil)能夠實現獲取系統運行的進程和系統利用率(內存,CPU,磁盤,網絡等),主要用於系統監控,分析和系統資源及進程的管理。2、IPy(http://github.com/haypo/p

《OpenCV3程式設計入門》——4.2 OpenCV常用資料結構函式(Point、Scalar、Size、Rect、cvtColor)

目錄 1、點的表示:Point類 2、顏色的表示:Scalar類 3、尺寸的表示:Size類 4、矩形的表示:Rect類 5、顏色空間轉換:cvtColor()函式 1、點的表示:Point類 Point類資料結構表示了二維座標系下的點,即由影象座標x和y指定的2D點

[OpenCV3程式設計入門讀書筆記]常用資料結構函式(3)

點的表示:Point類 //第一種表示方式 Point point; point.x = 10; point.y = 8; //第二種表示方式 Point point = Point(10,8); 顏色的表示:Scalar類 特別注意OopenCV裡面不是RGB,是BGR,所以下面的a

常用資料結構演算法

>  資料結構:線性表,堆疊,佇列,串,陣列,樹和二叉樹(紅黑樹是平衡二叉樹嗎?) > 演算法 - 演算法:遞迴演算法,二分查詢演算法 - 排序演算法 簡單排序:氣泡排序、選擇排序、插入排序 高階排序:快速排序、歸併排序、希爾排序 相關演算法知識:劃分、遞迴、二分查詢

資料結構演算法之——散列表

散列表的查詢效率並不能籠統地說成是 ,它和雜湊函式、裝載因子、雜湊衝突等都有關係。如果雜湊函式設計得不好,或者裝載因子過高,都可能會導致雜湊衝突發生的概率升高,查詢效率下降。 1. 如何設計雜湊函式? 雜湊函式設計的好壞,決定了雜湊衝突發生的概率,也直接決定了散列表的效能。那什麼才是好的雜湊函式

python---資料結構演算法複習

連結串列和二叉樹 # 1、連結串列反轉 class ListNode: def __init__(self, x): self.val = x self.next = None def nonrecurse(head): # 迴圈的方法反轉連結串列

第一章:Python資料結構演算法

第一章:Python資料結構和演算法 Python 提供了大量的內建資料結構,包括列表,集合以及字典。大多數情況下使用這些資料結構是很簡單的。 但是,我們也會經常碰到到諸如查詢,排序和過濾等等這些普遍存在的問題。 因此,這一章的目的就是討論這些比較常見的問題和演算法。 另外,我們也會

資料結構演算法3~5 時間複雜度空間複雜度

演算法效率的度量方法 容易想到的方法是:把演算法跑若干次,然後拿個計時器在旁邊計時。這種方法被稱為“事後諸葛亮”方法,也稱為事後分析估算方法。 事前分析估算方法:在計算機程式比編寫前,依據統計方法對演算法進行估算。 通過總結,我們發現,一個高階語言編寫程式在計算機上執行所消耗的時間取決於

python資料結構演算法【總結】

一。什麼是演算法 演算法是計算機處理資訊的本質,因為計算機程式本質上是一個演算法來告訴計算機確切的步驟來執行一個指定的任務。一般地,當演算法在處理資訊時,會從輸入裝置或資料的儲存地址讀取資料,把結果寫入輸出裝置或某個儲存地址供以後再呼叫。 通俗的來講,演算法就是你解決問題的思路。 二。演

【專欄】資料結構演算法之美-為什麼很多程式語言的陣列都是從 0 開始的

學習筆記 陣列的特徵 1.線性表 資料排成像一條線一樣的結構,資料之間只是簡單的前後關係。除了陣列是一種線性表結構外,連結串列、佇列和棧也是。與之對應的像二叉樹、堆、圖等就是非線性表。 2.使用連續

資料結構演算法06】2-3-4樹

    從第4節的分析中可以看出,二叉搜尋樹是個很好的資料結構,可以快速地找到一個給定關鍵字的資料項,並且可以快速地插入和刪除資料項。但是二叉搜尋樹有個很麻煩的問題,如果樹中插入的是隨機資料,則執行效果很好,但如果插入的是有序或者逆序的資料,那麼二叉搜尋樹的執行速度就變得很慢

學好資料結構演算法 —— 非線性結構

1、樹 樹是一種很常見的分線性資料結構,公司的組織架構,行政區劃結構等都是樹形結構。樹形結構裡常見的有樹和二叉樹。 樹的定義 樹是n(n>=0)個結點的有限集。 在任意一棵非空樹中: (1)有且僅有一個特定的稱為根(root)的結點 (2)當n>1時,其餘結點可分為m(m>0)個

C++ STL資料結構演算法

STL資料結構 STL資料結構[1] 以STL容器(Container)的形式提供,主要包括序列式容器(Sequence Containers)和關聯式容器(Associative Containers)兩大類。 序列式容器 array(build-

Python的四種資料結構

Python中的內建資料結構(Built-in Data Structure):列表list、元組tuple、字典dict、集合set,涵蓋的僅有部分重點。 一、列表list   list的顯著特徵: 列表中的每個元素都可變的,意味著可以對每個元素進行修改和刪除;

資料結構演算法分析之排序篇--歸併排序(Merge Sort)常用排序演算法時間複雜度比較(附贈記憶方法)

歸併排序的基本思想 歸併排序法是將兩個或以上的有序表合併成一個新的有序表,即把待排序序列分成若干個子序列,每個子序列是有序的。然後再把有序子序列合併為整體有序序列。注意:一定要是有序序列! 歸併排序例項: 合併方法: 設r[i……n]由兩個有序子表r

java資料結構演算法3章 簡單排序

氣泡排序、選擇排序、插入排序 public class SortDemo { public static void main(String[] args) { // T

Python常用的類方法

內建方法 說明 __init__(self,...) 初始化物件(例項),在建立新物件時呼叫 __del__(self) 解構函式,釋放物件,在物件被刪除之前呼叫,進行一些清理工作。 __new__(cls,*args,**kwd) 例項的生成操作 __str__(self)

python魔術方法 __repr____str__

repr() 和 str() 的區別 str() 1 使用str()函式時, 才呼叫__str_ __ 2 使用print()函式時 3 str() 的輸出追求可讀性,輸出格式要便於理解,適合用於輸出內容到使用者終端。 repr() 1 使用repr()時,才呼叫___re