深入理解 Python 中的排序函数

由于 Python2 和 Python3 中的排序函数略有区别,本文以Python3为主。

Python 中的排序函数有 sort ,sorted 等,这些适用于哪些排序,具体怎么用,今天就来说一说。

两个函数的区别

这儿直接给出这两个排序函数的区别

  • sort 可以直接改变所排序的变量,而 sorted 不会
  • sort 是 list 的内建函数,不能用于字典的排序,而 sorted 可以用于列表、元组、字典的排序

函数原型

sort

sort 函数原型如下,其中 L 是列表元素

L.sort(*, key=None, reverse=False)

参数解释:

  • key

key 也是接受一个函数,不同的是,这个函数只接受一个元素,形式如下

def f(a):
   return len(a)

key 接受的函数返回值,表示此元素的权值,sort 将按照权值大小进行排序,通常的我们会以 lambda 的形式展现出来,比如

key = lambda x : len(x)
  • reverse

接受False 或者True 表示是否逆序

sorted

sorted 函数原型如下,返回的是一个列表

sorted(iterable, *, key=None, reverse=False)

参数解释:

  • iterable

可以迭代的对象,可以是 list,tuple,dict.items(),dict.keys()或者自定义的类

  • key

sort 中的含义相同

  • reverse

sort 中的含义相同

实战演练

下面针对不同 Python 类型进行排序。

基础篇

list

# sort 内置函数
a = [14,4,2,19,37,23]
a.sort()    #改变原有列表
print(a)    #[2, 4, 14, 19, 23, 37]

# sorted 函数
b = [14,4,2,19,37,23]
c = sorted(b)    #不改变原有列表
print(b)         #[14, 4, 2, 19, 37, 23]   
print(c)         #[2, 4, 14, 19, 23, 37]

在这里,可以看出 sort 是没有返回值的,它会改变原有的列表,而 sorted 需要用一个变量进行接收,它并不会修改原有的列表

tuple

# sorted 函数
b = (14,4,2,19,37,23)
c = sorted(b)    #不改变原有列表
print(b)         #(14, 4, 2, 19, 37, 23)  
print(c)         #[2, 4, 14, 19, 23, 37]

这里⚠️注意了:对于元组来说,是没有内置函数 sort 的,只能使用 sorted 函数,而且 sorted 函数返回的是 list 类型哦,不能弄错了~~~

dict

这个类型相对复杂一点,多数情况下需要用到 key 参数,但是也不是很复杂,后面的进阶篇会详细讲解

# sorted 函数
a = {"today":2020,"pre":2019,"next":2021}
b = sorted(a.items())    #不改变原有列表
c = sorted(a.items(), key = lambda x:(x[1],x[0]))
print(b)         #[('next', 2021), ('pre', 2019), ('today', 2020)]
print(c)         #[('pre', 2019), ('today', 2020), ('next', 2021)]

如果不加 key 参数,就是默认按照键进行排序,加上 key 参数之后可以按照自己定义的规则来进行排序。

进阶篇

上面的基础篇讲的是最基本的排序方法,但是在实际应用中碰到的情况都比这个复杂怎么办?easy,进阶篇他来了。

自定义规则排序

自定义的排序主要通过参数 key 实现,我们来看看

from operator import itemgetter, attrgetter
student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]
a = sorted(student_tuples, key = lambda x : x[2])            # 使用 lambda
b = sorted(student_tuples, key = itemgetter(2))               # 使用 itemgetter
c = sorted(student_tuples, key = lambda x : (x[1],x[2]))     # 使用 lambda 进行多重排序 
d = sorted(student_tuples, key = itemgetter(1,2))             # 使用 itemgetter 进行多重排序
print(a)
print(b)
print(c)
print(d)

结果如下

[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

可以看到,key 关键字可以通过 lambda 和 itemgetter 方式实现,既可以实现单一字段的排序,还可以进行多重字段的排序,是不是很省时省力?

自定义类排序

前面讲的都是 Python 的内置类型,那么碰到我们自定义的类型该怎么去排序,其实道理是一样的,假设我们定义的类如下

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)]

我们再利用 operator 模块进行排序

from operator import itemgetter, attrgetter
a = sorted(student_objects, key=attrgetter('age'))
b = sorted(student_objects, key=attrgetter('grade', 'age'))
print(a)
print(b)
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

这个过程和前面的内置类型比较几乎一样,差别就在于使用函数分别是 itemgetter, attrgetter,简单的说 itemgetter 是以 index 的形势来获取相对应的值,而 attrgetter 是用 key 来获取相对应的值。具体的区别可以看看 这篇博客

这里有人就会问了?如果我想先以年龄升序,再以年级降序进行排序那该怎么写?这个就比较复杂了,有两种方法,听我娓娓道来。

  • 多重排序函数定义

这种方法定义起来需要有点技巧性,那上面的举例吧

def multisort(xs, specs):
    for key, reverse in specs:
        xs.sort(key=attrgetter(key), reverse=reverse)
    return xs
multisort(list(student_objects), (('grade', True), ('age', False)))
#[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

看的出来这种定义的技巧相对比较重要,对于非列表的比较和列表的比较大同小异,这里不赘述。

  • 使用老方法 cmp 比较函数

这个 cmp 参数到了 Python3 就给取消了不过还是可以通过其他方式给实现,过程繁琐,首先得定义一个比较函数

def cmp_to_key(mycmp):
    'Convert a cmp= function into a key= function'
    class K:
        def __init__(self, obj, *args):
            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
        def __ne__(self, other):
            return mycmp(self.obj, other.obj) != 0
    return K

这里的 mycmp 函数是自己定义的,我这定义的如下

def cmp(a, b):
    if a.age > b.age:
        return 1
    elif a.age < b.age:
        return -1
    else:
        return a.grade - b.grade
sorted(student_objects, key=cmp_to_key(cmp))
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

这俩结果是一样的,只不过后一种方法代码较长,但是思路简单,前面一种代码短,但是思路复杂一些,推荐前面一种方法,既可以锻炼思维,还能减少代码量。

参考

https://docs.python.org/3.7/library/functions.html?highlight=sorted#sorted

https://docs.python.org/3/library/functools.html#functools.cmp_to_key

https://www.jb51.net/article/147635.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值