首页 多维排序
文章
取消

多维排序

数组排序是很常见的一种操作。

一维数组排序大家都很熟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> l=[4,2,5,7,1]
>>> sorted(l)
[1, 2, 4, 5, 7]

>>> l=['a','apple','banana','car','kubernetes']
>>> sorted(l,key=len)
['a', 'car', 'apple', 'banana', 'kubernetes']

>>> sorted(l,key=lambda x:x[-1])
['a', 'banana', 'apple', 'car', 'kubernetes']

>>> sorted(l,key=lambda x:x[-1],reverse=True)
['kubernetes', 'car', 'apple', 'a', 'banana']

>>> class Human:
...     def __init__(self,age):
...             self.age=age
...     def __repr__(self):
...             return f"Human, age:{self.age}"
...
>>> l=[Human(10),Human(5),Human(34),Human(24)]
>>> sorted(l,key=lambda x:x.age)
[Human, age:5, Human, age:10, Human, age:24, Human, age:34]

谈到二维数组或更高维的多重排序时,刚开始自己就摸不到头脑了。

对于数据库表可以很简单地使用多重排序:

1
select a,b from table order by a,b;

但 python 不能使用 sql 对多维数组排序啊。

经过查找资料发现有如下有趣的用法:

lambda x: (x[0], x[1])

1
2
3
4
5
data = [[2, 3], [1, 3], [2, 1], [3, 5], [2, 6]]
# 多重排序,对元素 item,先按 item[0] 升序,再按 item[1] 升序
s = sorted(data, key=lambda x: (x[0], x[1]))
for i in s:
    print(i)

输出结果:

1
2
3
4
5
[1, 3]
[2, 1]
[2, 3]
[2, 6]
[3, 5]

当然可以对每个排序对象指定排序规则:

1
2
3
4
data = [[2, 3], [1, 3], [2, 1], [3, 5], [2, 6]]
s = sorted(data, key=lambda x: (x[0], -x[1]))
for i in s:
    print(i)

输出结果:

1
2
3
4
5
[1, 3]
[2, 6]
[2, 3]
[2, 1]
[3, 5]

局限性:上面的 - 表示逆序,只适用于数字的比较

attrgetter

对于类对象列表的按属性多重排序可以 attrgetter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from operator import attrgetter


class Human:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height

    def __repr__(self):
        return f"Human, age:{self.age}, height:{self.height}, name:{self.name}"


data = [Human('july', 12, 134), Human('andy', 13, 145),
        Human('lisa', 10, 125), Human('joe', 10, 130),
        Human('bob', 34, 178), Human('tom', 13, 145)]
s = sorted(data, key=attrgetter('age', 'name'))
for i in s:
    print(i)

输出结果:

1
2
3
4
5
6
Human, age:10, height:130, name:joe
Human, age:10, height:125, name:lisa
Human, age:12, height:134, name:july
Human, age:13, height:145, name:andy
Human, age:13, height:145, name:tom
Human, age:34, height:178, name:bob

局限性:只能按照指定属性升序排列

自定义比较函数

sorted() 函数中我们可以指定排序函数,但是这个函数只能接收一个参数,我们自定义比较函数时需要两个参数来互相比较,这就很矛盾。

python functools 包中有个辅助函数 cmp_to_key 可以帮我们解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from functools import cmp_to_key


def compare(x, y):
    # item[0] 升序
    if x[0] > y[0]:
        # 返回1,为了排序后 x[0] 在 y[0] 左侧
        return 1
    if x[0] < y[0]:
        return -1

    # 以下是 x[0]=y[0] 的情况

    # item[1] 降序
    if x[1] > y[1]:
        return -1
    if x[1] < y[1]:
        return 1
    return 0


data = [[2, 3], [1, 3], [2, 1], [3, 5], [2, 6]]
s = sorted(data, key=cmp_to_key(compare))
for i in s:
    print(i)

输出结果:

1
2
3
4
5
[1, 3]
[2, 6]
[2, 3]
[2, 1]
[3, 5]

cmp_to_key 的实现原理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
################################################################################
### cmp_to_key() function converter
################################################################################

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

cmp_to_key 将函数封装成了一个对象,利用了对象里的大小比较协议来实现排序。

这种方法虽然实现起来比较繁琐,但最灵活,实用性更广。

本文由作者按照 CC BY 4.0 进行授权