用 Python 排序資料的多種方法

icejoywoo發表於2016-06-27

【Python HOWTOs系列】排序

Author: Andrew Dalke and Raymond Hettinger
Release: 0.1

Python 列表有內建就地排序的方法 list.sort(),此外還有一個內建的 sorted() 函式將一個可迭代物件(iterable)排序為一個新的有序列表。

本文我們將去探索用 Python 做資料排序的多種方法。

排序基礎

簡單的升序排序非常容易:只需呼叫 sorted() 函式,就得到一個有序的新列表:

你也可以使用 list.sort() 方法,此方法為就地排序(並且返回 None 來避免混淆)。通常來說這不如 sorted() 方便——但是當你不需要保留原始列表的時候,這種方式略高效一些。

另外一個區別是 list.sort() 方法只可以供列表使用,而 sorted() 函式可以接受任意可迭代物件(iterable)。

Key 函式

list.sort() 和 sorted() 都有一個 key 引數,用於指定在作比較之前,呼叫何種函式對列表元素進行處理。 For example, here’s a case-insensitive string comparison: 例如,忽略大小寫的字串比較:

key 引數的值應該是一個函式,該函式接收一個引數,並且返回一個 key 為排序時所用。這種方法速度很快,因為每個輸入項僅呼叫一次 key 函式。

一種常見模式是使用物件的下標作為 key 來排序複雜物件。例如:

同樣的技巧也可以用在帶有命名屬性(named attributes)的物件上。例如:

上述的 key 函式模式是非常常見的,所以 Python 提供了一些更簡單快速的訪問屬性的函式。operator 模組有 itemgetter()、attrgetter() 和 methodcaller() 函式。 Using those functions, the above examples become simpler and faster: 使用這些函式,可以使上述的示例更加簡潔高效:

operator 模組方法允許多級排序。例如,可以先按 grade 排序,然後再按 age 排序:

list.sort() 和 sorted() 都有布林型的 reverse 引數,用來指定是否降序。例如,按 age 的降序來對學生資料進行排序:

排序是保證為穩定的,也就是說,當多條記錄擁有相同的 key 時,原始的順序會被保留下來。

注意到兩條 blue 記錄保持了原來的順序, 所以 (‘blue’, 1) 一定在 (‘blue’, 2) 之前。

這個非常棒的屬性允許你通過一系列排序來進行復雜排序。例如,學生資料先按 grade 升序,然後按 age 降序,優先排序 age,然後再按 grade 排序:

Python 使用的 Timsort 演算法由於可以有效利用資料集中已有的順序,因而可以高效地進行多級排序。

使用 Decorate-Sort-Undecorate 的舊方法

Decorate-Sort-Undecorate 的名稱來源於這種方法的三個步驟:

  • 第一步,初始的列表進行轉換,獲得用於排序的新值。
  • 第二步,將轉換為新值的列表進行排序。
  • 最後,還原資料並得到一個排序後僅包含原始值的列表。

例如,使用 DSU(譯註:Decorate-Sort-Undecorate的簡寫)方法,按 grade 來排序學生資料:

這一方法利用了元組按字典序 (lexicographically) 比較的特性;先比較第一項;如果第一項相同,則比較第二項,以此類推。

在很多情況下是不需要在處理後的列表(decorated list)包含原始下標 i,但是包含原始下標有兩個好處:

  • 排序是穩定的——如果有兩項有相同的 key,排序後的列表會保留他們的順序。
  • 原始項不需要是可比較的,因為處理後的元組最多使用前面兩項就可以決定排序。例如,原始列表中包含無法直接比較的複數。

這個方法還有另外一個名字,是以 Randal L. Schwartz 的名字來命名的 Schwartzian 變換,因為他使得這個變換在 Perl 程式設計師中得以流行。

在 Python 排序提供 key 函式之後,這個技巧已經不常用了。

使用 cmp 引數的舊方法

本篇指南中給出的方法都假設 Python 2.4 或更新版本。在 2.4 之前,sorted() 和 list.sort() 是沒有 key 引數的。但是,在所有的 Py2.x 版本都支援 cmp 引數來處理使用者自定義排序函式。

在 Py3.0 中,cmp 引數已經被完全移除(作為簡化和統一語言的一部分,去除排序和 cmp() 魔法方法之間的衝突)。

在 Py2.x 中,sort 允許傳入一個可選函式,會在進行比較的時候呼叫。函式必須接受兩個引數進行比較,然後返回負數表示小於,返回 0 表示相等,返回正數表示大於。例如,我們可以這樣:

或者你也可以反轉比較順序:

當從 Python 2.x 移植程式碼到 3.x 時,可能會出現需要將使用者提供的排序函式轉換為 key 函式的情況。下面的包裝器可以輕鬆做到:

轉換為 key 函式,僅需要包裝舊的比較函式即可:

在 Python 3.2 中,functools.cmp_to_key() 函式已經新增到標準庫的 functools 模組中。

其他要點

  • 針對時區相關排序,使用 locale.strxfrm() 作為 key 函式,或者使用 locale.strcoll() 作為比較函式。
  • reverse 引數仍然保持排序穩定性(以便相同 key 的項保留原順序)。有趣的是,無需傳入引數,通過兩次呼叫內建的 reversed() 函式,可以模擬出相同的效果:
  • 在兩個物件進行比較時,sort 使用的是 lt() 方法。所以,只需要為類新增 lt() 方法,就可以為類加入排序順序:
  • key 函式不需要直接依賴於排序的物件。key 函式可以訪問外部資源。例如,如果學生的成績儲存在字典中,字典中的資料可以給單獨的一個學生名字排序:

相關文章