Python应用——自定义排序全套方案

本文始发于我的公众号:TechFlow,原创不易,求个关注python


今天的这篇文章和你们聊聊Python当中的排序,和不少高级语言同样,Python封装了成熟的排序函数。咱们只须要调用内部的sort函数,就能够完成排序。可是实际场景当中,排序的应用每每比较复杂,好比对象类型,当中有多个字段,咱们但愿按照指定字段排序,或者是但愿按照多关键字排序,这个时候就不能简单的函数调用来解决了。编程


字典排序


咱们先来看下最多见的字典排序的场景,假设咱们有一个字典的数组,字典内有多个字段。咱们但愿可以根据字典当中的某一个字段来进行排序,咱们用实际数据来举个例子:数组

kids = [
    {'name': 'xiaoming', 'score': 99, 'age': 12},
    {'name': 'xiaohong', 'score': 75, 'age': 13},
    {'name': 'xiaowang', 'score': 88, 'age': 15}
]
复制代码

这里的kids是一个dict类型的数组,dict当中拥有name, score和age三个字段。假设咱们当下但愿可以按照score来排序,应该怎么办呢?函数

对于这个问题,解决的方案有不少,首先,咱们可使用上一篇文章当中提到的匿名函数来指定排序的。这里的用法和上篇文章优先队列的用法是同样的,咱们直接来看代码:工具

sorted(kids, key=lambda x: x['score'])
复制代码

在匿名函数当中咱们接收的x是kids当中的元素,也就是一个dict,因此咱们想要指定咱们但愿的字段,须要用dict访问元素的方法,也就是用中括号来查找对应字段的值。spa

假如咱们但愿按照多关键字排序呢?code

首先介绍一下多关键字排序,仍是用上面的数据打比方。在上面的例子当中,各个kid的score都不同,因此排序的结果是肯定的。但若是存在两我的的score相等,我但愿年龄小的排在前面,那么应该怎么办呢?咱们分析一下能够发现,本来是按照分数从小到大排序,但有可能会出现分数相等的状况。这个时候,咱们但愿可以按照在分数相等的状况下来比较年龄,也就是说咱们但愿根据两个关键字来排序,第一个关键字是分数,第二个关键字是年龄。orm

因为Python当中支持tuple和list类型的排序,也就是说咱们能够直接比较[1, 3]和[1, 2]的大小关系,Python会自动一次比较两个数组当中的元素的大小。若是相等就自动日后比较,直到出现不等或者结束为止。cdn

明白了这点,其实就很好办了。咱们只要在匿名函数当中稍稍修改,让它返回的结果增长一个字段便可。对象

sorted(kids, key=lambda x: (x['score'], x['age']))
复制代码

itemgetter


除了匿名函数,Python也有自带的库能够解决这个问题。用法和匿名函数很是接近,使用起来稍稍容易一些。

它就是operator库当中的itemgetter函数,咱们直接来看代码:

from operator import itemgetter

sorted(kids, key=itemgetter('score'))
复制代码

若是是多关键字也能够,传入多个key便可:

sorted(kids, key=itemgetter('score', 'age'))
复制代码

对象排序


咱们接下来看一下对象的自定义排序,咱们首先把上面的dict写成对象:

class Kid:
    def __init__(self, name, score, age):
        self.name = name
        self.score = score
        self.age = age

    def __repr__(self):
        return 'Kid, name: {}, score: {}, age:{}'.format(self.name, self.score, self.age)
复制代码

为了方便观察打印结果,咱们重载了__repr__方法,能够简单地将它当作是Java当中的toString方法,这样咱们能够指定在print它的时候的输出结果。

一样,operator当中也提供了对象的排序因子函数,用法上和itemgetter同样,只是名字不一样。

from operator import attrgetter

kids = [Kid('xiaoming', 99, 12), Kid('xiaohong', 75, 13), Kid('xiaowang', 88, 15)]

sorted(kids, key=attrgetter('score'))
复制代码

咱们也可使用匿名函数lambda来实现:

sorted(kids, key=lambda x: x.score)
复制代码

自定义排序


到这里尚未结束,由于仍然存在一些问题解决不了。虽然咱们实现了多关键字排序,可是还有一个问题解决不了,就是排序的顺序问题。

咱们能够在sorted函数的参数当中传入reverse=True来控制是正序仍是倒叙,可是若是我使用多关键字,想要按照某个关键字升序,某个关键字降序怎么办?举个例子,好比说咱们想要按照分数降序,年龄升序就没办法经过reverse来解决了,这就是当前解决不了的问题。

那应该怎么办呢?

这个时候就须要终极排序杀器上场了,也就是标题当中所说的自定义排序。也就是说咱们本身实现一个定义元素大小的函数,而后让sorted来调用咱们这个函数来完成排序。这也是C++和Java等语言的用法。

自定义的函数并不难写,咱们随手就来:

def cmp(kid1, kid2):
    return kid1.age < kid2.age if kid1.score == kid2.score else kid1.score > kid2.score
复制代码

若是看不明白,也不要紧,我写成完整版:

def cmp(kid1, kid2):
    if kid1.score == kid2.score:
        return kid1.age < kid2.age
    else:
        return kid1.score > kid2.score
复制代码

写完了以后,尚未结束,这个函数是不能直接投入使用的,他和咱们以前提到的lambda匿名函数是不同的。以前的匿名函数只是用来指定字段的,因此咱们不能直接将这个函数传递给key,还须要在外面包一层加工处理才能够。不过这一层处理函数Python也已经有现成的工具了,咱们能够直接调用,它在functools里,咱们来看代码:

from functools import cmp_to_key

sorted(kids, key=cmp_to_key(cmp))
复制代码

咱们来看一下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
复制代码

咱们能够看到,在函数内部,它其实定义了一个类,而后在类当中重载了比较函数,最后返回的是一个重载了比较函数的新的对象。这些__lt__, __gt__函数就是类当中重载的比较函数。好比__lt__是小于的判断函数,__eq__是相等的函数。那么问题来了,咱们能不能直接在Kid类当中重载比较函数呢,这样就能够直接排序了。

答案是肯定的,咱们固然能够这么办,实际上这也是面向对象当中很是经常使用的作法。相比于自定义比较函数,咱们每每更倾向于在类当中定义好优先级。Python当中实现的方法也很简单,就是咱们手动实现一个__lt__函数,sorted默认会将小的元素排在前面,因此咱们只用实现__lt__一个函数就够了。这个函数当中传入的参数是另外一个对象,咱们直接在函数里面写清楚比较逻辑就好了。返回True表示当前对象比other小,不然比other大。

咱们附上完整代码:

class Kid:
    def __init__(self, name, score, age):
        self.name = name
        self.score = score
        self.age = age

    def __repr__(self):
        return 'Kid, name: {}, score: {}, age:{}'.format(self.name, self.score, self.age)

    def __lt__(self, other):
        return self.score > other.score or (self.score == other.score and self.age < other.age)
复制代码

实现了比较函数以后,咱们直接调用sorted,不用任何其余传参就能够对它进行排序了。

今天的内容虽然难度不大,可是在咱们平常编程当中很是经常使用,常常会出现须要对复杂的对象和内容进行排序的状况,因此但愿你们都掌握,由于必定会派上用场的。

今天的文章就是这些,若是以为有所收获,请顺手扫码点个关注吧,大家的举手之劳对我来讲很重要。

相关文章
相关标签/搜索