聊聊python中排序:sort與sorted

0 前言

有時候我們需要自定義排序的規則,但是又不想自己再寫一遍排序函數,所以庫中的排序函數一般都支持我們自定排序規則,然後以函數的形式傳遞給排序函數,就完成了自定義排序的需求。之前一直用C++,知道C++中的排序函數sort是可以實現這種需求的。現在轉到python3突然發現python3提供的sort函數沒有了cmp這個參數(python2是有的),但是幸運的是,我們還可以通過其他的方法實現這種自定義排序規則的需求。

1 python2中的sort和sorted

1.1 sort()函數

sort函數的定義如下:
sort(self, cmp=None, key=None, reverse=False)

  • self:表示list自身
  • cmp:自定的比較函數
  • key:指定元素在比較之前要調用的函數,並且這個函數接受一個參數,返回一個作爲排序依據的key。
  • reverse:當爲False時,表示增序排列,爲True時爲降序排列。

sort函數僅僅對列表對象進行排序,會改變list元素自身的順序,沒有返回值也可以認爲返回值爲None。調用的形式爲list.sort()

python2中使用sort函數自定義排序函數來進行排序:

a = [('ZhangSan',24),('Lisi', 22) , ('WangWu', 20)]

#python中升序排序的比較函數的邏輯
def cmp(a, b):
    if a < b:
        return -1
    elif a > b:
        return 1
    else:
        return 0
print a
a.sort(cmp=cmp, key=lambda x: x[1])
print a

輸出結果:

[('ZhangSan', 24), ('Lisi', 22), ('WangWu', 20)]
[('WangWu', 20), ('Lisi', 22), ('ZhangSan', 24)]

1.2 sorted()函數

sorted()函數的定義:
sorted(iterable, cmp=None, key=None, reverse=False)

  • iterable表示可迭代的類型
  • 其他的參數和sort()函數一致

sorted函數與sort函數相比有兩點主要的不同:

  • sorted函數不會改變原來的可迭代的集合,而是會返回一個新的排序好的列表(list)
  • sort函數只能用於list,sorted函數可以用於任意可迭代的類型

python2中使用sorted函數自定義排序函數來進行排序:

a = [('ZhangSan',24),('Lisi', 22) , ('WangWu', 20)]

#python中升序排序的比較函數的邏輯
def cmp(a, b):
    if a < b:
        return -1
    elif a > b:
        return 1
    else:
        return 0
print a
b = sorted(a, cmp, key=lambda x: x[1])
print a
print b

2 python3中的sort和sorted

2.1 在python3中使用cmp函數

在python3中sort函數和sorted函數都有所變化,他們的cmp參數都被去掉了。沒有了cmp函數怎麼進行自定義排序呢?其實python提供了另一種方法。就是使用functools.cmp_to_key函數將比較函數轉換爲key的參數。

import functools

a = [('ZhangSan',24),('Lisi', 22) , ('WangWu', 20), ('WangWu', 19)]

#python中升序排序的比較函數的邏輯
def cmp(a, b):
    if a < b:
        return -1
    elif a > b:
        return 1
    else:
        return 0
print (a)
a.sort(key=functools.cmp_to_key(cmp))
print (a)
[('ZhangSan', 24), ('Lisi', 22), ('WangWu', 20), ('WangWu', 19)]
[('Lisi', 22), ('WangWu', 19), ('WangWu', 20), ('ZhangSan', 24)]

可以看到,對於元組,默認按照第一個元素排序,第一個元素相同的話會比較第二個元素,依次往後比較。cmp函數需要兩個參數,functools模板中的functools.cmp_to_key()就可以把原來有兩個參數的cmp轉化成了符合key參數要求的一個參數的函數。
cmp_to_key函數的源碼如下:

def cmp_to_key(mycmp):
    """Convert a cmp= function into a key= function"""
    class K(object):
        __slots__ = ['obj']
        def __init__(self, obj):
            self.obj = obj
        def __lt__(self, other):
            return mycmp(self.obj, other.obj) < 0
        def __gt__(self, other):
            return mycmp(self.obj, other.obj) > 0
        def __eq__(self, other):
            return mycmp(self.obj, other.obj) == 0
        def __le__(self, other):
            return mycmp(self.obj, other.obj) <= 0
        def __ge__(self, other):
            return mycmp(self.obj, other.obj) >= 0
        __hash__ = None
    return K

返回的是一個類,在 sort 內部,類接收一個參數構造一個實例,然後實例通過重載的方法來進行比較。
sorted函數同樣支持上面的用法。

2.2 python3中自定義排序

上面介紹的是怎樣在python3中使用cmp函數來自定義排序,cmp函數有兩個參數,是python2的產物,在python中提倡使用key function,key function接收一個參數。我們完全可以自定義的排序規則定義爲key function,而不是定義爲cmp函數再使用cmp_to_key轉換成key function。
必須再次明確key_function的作用:指定元素在比較之前要調用的函數,並且這個函數接受一個參數,返回一個作爲排序依據的key。
下面是幾個例子來幫助,我們適應使用key_function這種方式來自定義排序規則。
例子1:

my_alphabet = ['a', 'b', 'c']

def custom_key(word):
   numbers = []
   for letter in word:
      numbers.append(my_alphabet.index(letter))
   return numbers
# python中的整數列表能夠比較大小
# custom_key('cbaba')==[2, 1, 0, 1, 0]

x=['cbaba', 'ababa', 'bbaa']
x.sort(key=custom_key)
print(x)
['ababa', 'bbaa', 'cbaba']

例子2:

students = [('john', 'A', 15), ('jane', 'B', 12), ('dave','B', 10)]
ns = sorted(students,key=lambda x: x[2]) #按照年齡來排序
print(ns)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

例子3:使用對象的屬性作爲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)   # sort by age
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

例子4:使用多個多個關鍵字進行排序

>>> list = [('d',3),('a',5),('d',1),('c',2),('d',2)]
>>> print (sorted(list, key = lambda x:(x[0],x[1])))
[('a', 5), ('c', 2), ('d', 1), ('d', 2), ('d', 3)]

例子5:
默認sorted([True, False])==[False, True] (False=0 < True=1)
一個字符串排序,排序規則:小寫<大寫<奇數<偶數

#元組內(e1, e2, e3)的優先級排列爲: e1 > e2 > e3
s = 'asdf234GDSdsf23'
ns = sorted(s, key=lambda x: (x.isdigit(),x.isdigit() and int(x) % 2 == 0,x.isupper(),x)
print(ns)
#input: 'asdf234GDSdsf23'
#output: ['a', 'd', 'd', 'f', 'f', 's', 's', 'D', 'G', 'S', '3', '3', '2', '2', '4']
  • x.isdigit()的作用是把數字放在後邊(True),字母放在前面(False).
  • x.isdigit() and int(x) % 2 == 0的作用是保證數字中奇數在前(False),偶數在後(True)。
  • x.isupper()的作用是在前面基礎上,保證字母小寫(False)在前大寫在後(True).
  • 最後的x表示在前面基礎上,對所有類別數字或字母排序。

小結:可以看到key_function非常靈活,我們可以定義各種各樣的排序規則。其實python2中同樣具有sort和sorted函數也是有key參數的,同樣可以使用key function定義排序規則。

3 operator.itemgetter或者operator.attrgetter

首先介紹一下:operator模塊。operator模塊封裝了python的一系列操作符對應的函數,某些情況下我們需要爲函數傳遞一個操作符對應的函數,而不能直接傳遞一個操作符
這裏僅僅介紹operator下面三種操作符在自定義排序中的應用。

  • operator.itemgetter() ----- 通過下標獲取屬性
  • operator.attrgetter() ----- 通過參數獲取屬性
  • operator.methodcaller() -----通過函數名獲取函數,python 2.5 被引入,下文詳細介紹
from operator import itemgetter, attrgetter
student_tuples = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10),]
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),]


print(sorted(student_tuples, key=itemgetter(2)))
print(sorted(student_objects, key=attrgetter('age')))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

這個操作模塊也允許多層次的進行排序,例如可以先排序 “成績grand” 再排序 “年齡age”

print(sorted(student_tuples, key=itemgetter(1,2)))
print(sorted(student_objects, key=attrgetter('grade', 'age')))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

itemgetter和attrgetter比較好理解,methodcaller其實不太好理解,關於methodcaller的詳細介紹參考博客:python中的operator模塊
下面看一個例子:

from operator import methodcaller
messages = ['critical!!!', 'hurry!', 'standby', 'immediate!!']
print(sorted(messages, key=methodcaller('count', '!')))
['standby', 'hurry!', 'immediate!!', 'critical!!!']

上面的例子完成的功能是,根據字符串中!的數量對字符串排序,!數量少的排在前面,多的排在後面。
爲了幫助理解以上代碼,補充一個知識,python的count()函數:
描述: count() 方法用於統計字符串裏某個字符出現的次數。可選參數爲在字符串搜索的開始與結束位置。
語法: str.count(sub, start= 0,end=len(string))
參數:

  • sub – 搜索的子字符串
  • start – 字符串開始搜索的位置。默認爲第一個字符,第一個字符索引值爲0。
  • end – 字符串中結束搜索的位置。字符中第一個字符的索引爲 0。默認爲字符串的最後一個位置。

4 排序的穩定性

從python2.2版本開始,python的排序是保障穩定性的,意思就是說,當複雜的排序中,對象有相同的key的時候,會保持原有的相對順序不變:

data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
print(sorted(data, key=itemgetter(0)))
[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]

5 一個應用實例

具有串文件名['file1.txt', 'file2.txt', 'file10.txt']需要按照文件名的數字後綴對文件名進行排序:

import re

re_digits = re.compile(r'(\d+)')


def emb_numbers(s):
    pieces = re_digits.split(s)
    pieces[1] = map(int, pieces[1])
    return pieces


def sort_strings_with_emb_numbers(alist):
    aux = [(emb_numbers(s), s) for s in alist]
    aux.sort()
    return [s for __, s in aux]


def sort_strings_with_emb_numbers2(alist):
    return sorted(alist, key=emb_numbers)


filelist = 'file10.txt file2.txt file1.txt'.split()

print (filelist)

print ('--DSU排序')
print (sort_strings_with_emb_numbers(filelist))


print ('--內置DSU排序')

print (sort_strings_with_emb_numbers2(filelist) )
['file10.txt', 'file2.txt', 'file1.txt']
--DSU排序
['file1.txt', 'file10.txt', 'file2.txt']
--內置DSU排序
['file1.txt', 'file10.txt', 'file2.txt']

參考文章:

  1. Python 排序—sort與sorted學習
  2. Python3中排序函數sort()和sorted()
  3. python3 sorted 如何自定義排序標準
  4. python3 sort 去除了cmp怎麼自定義排序(一個降序,一個升序)
  5. Python3自定義key函數排序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章