文:鄭元春
一個有著美好夢想的coder!
人生苦短,我用Python。
P.S. 個人認為,查找和排序是算法的核心,同時又是在實際應用中使用率最高。今天就說說Python里面的排序吧!
ox01:string的排序函數 -- s.sort()
s.sort([cmp[, key[, reverse]]])
sort the items of s in place
喜歡原版英文文檔的同學就直接看原版的英文文檔吧(為了方便閱讀,我把英文原版文檔放在了本文最后面),文檔還是看原版的比較準確。
sort()函數和reverse()函數都是原地操作的(就是不會返回數據,直接在原有的數據上面操作,執行sort或者是reverse之后,你原始的數據將會被改變)。除了最基本的功能之外,sort()函數還有可選擇的參數來完成一些高級或是定制的排序操作。排序的核心就是你定義的比較規則(cmp函數),只有比較才能分出高低來。cmp是用戶自己指定比較規則,這個函數會接受兩個參數(也就是list的element),通過比較你的第一個參數與第二個參數的關系(小,等于,大)規則函數會返回負數,0或正數。
- 默認情況下,直接sort()
將字符串list排序,注意的是需要注意大小寫問題,大寫字符要比相應的小寫字符靠前,原因是大寫字符的Ascii值要靠前。Ascii[A]=65, Ascii[a]=97.
#假設我們有一個字符串list(網站的所有用戶的用戶名),現在需要按照字母表順序進行排序。
usernameList=['Lily','Nancy','John','Micky','Andrew','XiaoMingZhang','LiHua']
usernameList.sort()
#結果是:
['Andrew', 'John', 'LiHua', 'Lily', 'Micky', 'Nancy', 'XiaoMingZhang']
#如果是想按照倒序排的話,需要將reverse設置為True
usernameList=['Lily','Nancy','John','Micky','Andrew','XiaoMingZhang','LiHua']
usernameList.sort(reverse=True)
#結果是:
['XiaoMingZhang', 'Nancy', 'Micky', 'Lily', 'LiHua', 'John', 'Andrew']
#大小寫混亂的情況
usernameList=['lily','Nancy','john','micky','andrew','XiaoMingZhang','LiHua']
usernameList.sort()
#結果是(大寫的先排):
['LiHua', 'Nancy', 'XiaoMingZhang', 'andrew', 'john', 'lily', 'micky']
- 選取部分字符排序(注意是字符,不是字符串)
這時候你會看到排序的規則是按照第一個字符字典序排列,當第一個字符一樣的情況下就去排第二個字符,依次類推。有時候我們需要按照某個位置排序,或是將字符串某個位置之后的排序(因為有的情況下所有的字符串會有一個相同/不同的前綴,比如區號,自定義代碼等會對排序造成干擾)。這時候就使用key來指定參加排序的關鍵字了。
usernameList=[
'EN_lily',
'EN_Nancy',
'EN_John',
'EN_Micky',
'EN_Andrew',
'CN_XiaoMingZhang',
'CN_LiHua']
usernameList.sort()
#結果是:
['CN_LiHua',
'CN_XiaoMingZhang',
'EN_Andrew',
'EN_John',
'EN_Micky',
'EN_Nancy',
'EN_lily']
usernameList.sort(key=lambda x: x[3:])
#結果是:
['EN_Andrew',
'EN_John',
'CN_LiHua',
'EN_Micky',
'EN_Nancy',
'CN_XiaoMingZhang',
'EN_lily']
同時,前面提到的字符大小寫問題就可以解決了,這里的key的功能更像是一個map函數的功能,我先將原始的待排序的數據做下預處理,可以是挑選一部分字符排序 ,可以是將字符轉換成統一大小寫。也可以將字符映射成其他的,只要是你映射之后可以被cmp規則處理就行。
- 指定正序還是逆序
reverse參數指定的就是正序還是逆序,True為正序。在介紹第一個參數的時候已經介紹過了。
總結:
對于字符串的排序更多用在文本處理或者是簡單的操作中,因為上面舉得例子中如果在互聯網應用中的話,這些操作在數據庫就能夠直接排序了,但是有的時候你必須使用Python的排序方法,比如你的后臺開啟多線程抓取數據,或是后臺從不同的網站抓數據,可以直接就排序了。
0x02.sorted()
sorted(iterable[, cmp[, key[, reverse]]])
Return a new sorted list from the items in iterable.
對于sort()函數來說,一般用在list上。但Python內建的函數sorted()可用的范圍就比較大了,適用于所有的iterable data。相比于sort()函數一個最大的不同就是sorted()函數不會在原始的數據上做"手腳",他會返回一個排好的list。
iterable:是可迭代類型;
cmp:用于比較的函數,比較什么由key決定;
key:用列表元素的某個屬性或函數進行作為關鍵字,有默認值,迭代集合中的一項;
reverse:排序規則. reverse = True 降序 或者 reverse = False 升序,有默認值。
返回值:是一個經過排序的可迭代類型,與iterable一樣。
其實大多數的使用方式和list的sort()函數一樣。
- 最簡單的使用方式
>>> sorted([5, 2, 3, 1, 4])
[1, 2, 3, 4, 5]
- key 函數
從2.4版本之后,list.sort()和sorted()函數都同時添加了key參數,指定iterable data中需要排序的數據。
>>> sorted("This is a test string from Andrew".split(), key=str.lower)
['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
key是個只接受單個參數的函數,它返回一個用來進行排序的key,之后的排序就是使用這個key進行排序,相當于上面提到的數據預處理了。官方文檔里面說這項技術是fast
的,因為對于每個輸入這個key函數只執行一次。看完下面的例子你就會知道為什么我會在前面的段落中說這個key函數像是一個篩選操作。
student_tuples = [
('john', 'A', 15),
('jane', 'B', 12),
('dave', 'B', 10),
]
>>> sorted(student_tuples, key=lambda student: student[2]) # 年齡排序
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
學過數據庫的同學一定會聯想到order by 操作了吧!是的,在數據庫操作中,我們可以select多column的數據,然后通過指定一行或是多行進行排序。同時不僅能指定tuple的排序key,還能對對象list的排序key進行指定(下面的例子)
>>> class Student:
def __init__(self, name, grade, age):
self.name = name
self.grade = grade
self.age = age
def __repr__(self):
return repr((self.name, self.grade, self.age))
>>> student_objects = [
Student('john', 'A', 15),
Student('jane', 'B', 12),
Student('dave', 'B', 10),
]
>>> sorted(student_objects, key=lambda student: student.age)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
- 更簡單的使用key函數
不得不說。Python真是為碼農考慮的太周到了,上面通過對象的attribute進行key排序的已經很人性化了,但是,Python提供了itemgetter和attrgetter兩個方法來進行更人性化的操作。廢話不多說,看例子。
from operator import itemgetter, attrgetter
>>>
>>> sorted(student_tuples, key=itemgetter(2))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
>>>
>>> sorted(student_objects, key=attrgetter('age'))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
如果是你需要排序的iterable data里面每個element是tuple的話,那就使用itemgetter函數進行key的篩選好了,如果iterable data里面的每個element是對象的話那就使用attrgetter函數進行key篩選好了,這樣你連那個lambda函數的功夫也就省了(對于我這種小coder來說,兩者好像沒什么區別嘛)
- key的多級排序
上面提到過的數據庫會運行coder指定多個column對select結果進行多級排序,同樣Python的itemgetter和attrgetter也是支持多級排序的。
>>> sorted(student_tuples, key=itemgetter(1,2))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
>>>
>>> sorted(student_objects, key=attrgetter('grade', 'age'))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
- key的另一種“篩選”數據的方式
其實,key的作用除了簡單的“篩選”功能之外,還有很多的其他有意思的功能。將要排序的原始數據映射到另一個數據空間中,更像一個map函數的操作。
通過結合operator.methodcaller()函數,下面的例子比較有趣:
messages = ['critical!!!', 'hurry!', 'standby', 'immediate!!']
>>> sorted(messages, key=methodcaller('count', '!'))
['standby', 'hurry!', 'immediate!!', 'critical!!!']
從這個例子來看的話,對iterable data中的每個元素(這里是字符串)進行count("!")操作,將返回的“!”個數作為排序的依據。
- 正序+逆序
使用reverse參數就能控制逆序還是順序了,和上一段用法一樣。
總結:
Python的排序真的是讓人喜歡的很啊,從最初始的基本功能,到各種高級功能,同時還支持key函數和自己定義的cmp函數,這些用戶自行編寫的功能函數很好的擴展了sort的功能,說其“三頭六臂”也不為過啊!
key函數目的是找出排序依據,通過對原始數據預處理(選數據的一部分,計算元素個數等)來找出排血的基礎,這才是key的真正意義吧,有的時候你的排序操作有可能就不是對原始數據做排序處理了。
cmp函數的目的是制定排序規則,通過用戶自定義的排序函數,可以實現定制功能,只要你的返回符合他的規則就行了。
reverse參數簡單的使用True或者是False就能翻轉排序結果,用戶體驗很好。
文章的最后放上官方的文檔作為原始的參考。
The sort() and reverse() methods modify the list in place for economy of space when sorting or reversing a large list. To remind you that they operate by side effect, they don’t return the sorted or reversed list.
The sort() method takes optional arguments for controlling the comparisons.
cmp specifies a custom comparison function of two arguments (list items) which should return a negative, zero or positive number depending on whether the first argument is considered smaller than, equal to, or larger than the second argument: cmp=lambda x,y: cmp(x.lower(), y.lower()). The default value is None.
key specifies a function of one argument that is used to extract a comparison key from each list element: key=str.lower. The default value is None.
reverse is a boolean value. If set to True, then the list elements are sorted as if each comparison were reversed.
In general, the key and reverse conversion processes are much faster than specifying an equivalent cmp function. This is because cmp is called multiple times for each list element while key and reverse touch each element only once. Use functools.cmp_to_key() to convert an old-style cmp function to a key function.
Changed in version 2.3: Support for None as an equivalent to omitting cmp was added.
Changed in version 2.4: Support for key and reverse was added.
Starting with Python 2.3, the sort() method is guaranteed to be stable. A sort is stable if it guarantees not to change the relative order of elements that compare equal — this is helpful for sorting in multiple passes (for example, sort by department, then by salary grade).
The optional arguments cmp, key, and reverse have the same meaning as those for the list.sort() method (described in section Mutable Sequence Types).
cmp specifies a custom comparison function of two arguments (iterable elements) which should return a negative, zero or positive number depending on whether the first argument is considered smaller than, equal to, or larger than the second argument: cmp=lambda x,y: cmp(x.lower(), y.lower()). The default value is None.
key specifies a function of one argument that is used to extract a comparison key from each list element: key=str.lower. The default value is None (compare the elements directly).
reverse is a boolean value. If set to True, then the list elements are sorted as if each comparison were reversed.
In general, the key and reverse conversion processes are much faster than specifying an equivalent cmp function. This is because cmp is called multiple times for each list element while key and reverse touch each element only once. Use functools.cmp_to_key() to convert an old-style cmp function to a key function.
The built-in sorted() function is guaranteed to be stable. A sort is stable if it guarantees not to change the relative order of elements that compare equal — this is helpful for sorting in multiple passes (for example, sort by department, then by salary grade).