Python:如何按不同顺序对多个属性的自定义对象列表进行排序?

问题描述 投票:0回答:4

例如,如果我有一个

Person
课程

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def __repr__(self) -> str:
        return f"({self.name}, {self.age})"

Person

的列表
persons = [
    Person("Bob", 25),
    Person("Alice", 25),
    Person("Charlie", 23),
    Person("Dave", 25),
]

我可以按

age
升序对列表进行排序,如果出现平局,则使用以下方法按
name
升序排序:

sorted_persons = sorted(persons, key=lambda p: (p.age, p.name))

问题:

但是,我正在寻找一种按

age
升序对列表进行排序的方法,如果年龄相同,则按
name
降序排序。我怎样才能用Python实现这个目标?

我想出了一种解决方案,如下所示,但看起来有点不优雅。是否有更简洁的方法来编写可以处理所有三种情况(即小于、等于和大于)的字符串比较方法?例如,Java 有一个

s1.compareTo(s2)
方法可以使此类比较变得简单。

这是我目前正在使用的解决方案:

from functools import cmp_to_key


def compare(p1, p2):
    cmp = p1.age - p2.age
    if cmp != 0:
        return cmp
    if p1.name < p2.name:
        return 1
    elif p1.name > p2.name:
        return -1
    return 0


sorted_persons = sorted(persons, key=cmp_to_key(compare))

此代码正确地首先按

persons
升序对
age
列表进行排序,然后当年龄相等时按
name
降序排序。然而,我觉得应该有一种更干净、更 Pythonic 的方式来处理这个问题。有什么建议吗?

python sorting comparator string-comparison
4个回答
2
投票

这段代码应该可以做到。我运行了它,输出看起来是正确的

sorted_persons = sorted(persons, key=lambda p: (-p.age, p.name), reverse=True)

2
投票

Python used 具有

cmp
内置函数,可以完成您想要的操作。

还有一个名为

cmp
的关键字参数,用于
sort
sorted
,您可以在此处使用它,但它在 Python 3.0+ 中被删除,因为
key
通常比
cmp
性能更高。不幸的是,你发现了一个完全不是这样的地方。

但是,您可以在这里使用一个技巧:您可以使用元组比较,并且可以交换名称,因此:

def compare(p1, p2):
    a = p1.age, p2.name
    b = p2.age, p1.name

然后你就可以比较这两个。还有一个技巧可以利用 True 和 False 分别是值为 1 和 0 的整数这一事实来很好地生成 -1, 0, 1:

    return (a > b) - (a < b)

如果

>
<
是正常定义的,则最大一侧将导致 True,另一侧将导致 False;
True - False == 1
False - True == -1
,还有
False - False == 0


2
投票

另一种解决方案:

为了排序,您可以定义自定义

str
类,在其中重写
__lt__
魔术方法(小于):

class reverse_cmp_string(str):
    def __lt__(self, other):
        return not str.__lt__(self, other)

sorted_persons = sorted(persons, key=lambda p: (p.age, reverse_cmp_string(p.name)))
print(sorted_persons)

打印:

[(Charlie, 23), (Dave, 25), (Bob, 25), (Alice, 25)]

2
投票

Python的排序是稳定的。这意味着具有相同排序键的项目的顺序将保留在结果列表中。

您可以通过按步骤排序来利用这一点,从最不重要的顺序开始:

sorted_persons = sorted(persons, key=lambda p: p.name, reverse=True)
sorted_persons.sort(key=lambda p: p.age)

您还可以将 cmp_to_key 简化为 lambda(但这不是不同的解决方案):

sorted_persons = sorted(persons,key=cmp_to_key(
                 lambda a,b: a.age-b.age or (a.name<b.name)-(b.name<a.name)))

对于更通用的解决方案,您可以创建一个类来处理直接在 key=lambda 的返回元组中表示的所有降序键排序:

class descending():
    def __init__(self,value):  self.value = value
    def __lt__(self,other):    return self.value > other.value

用途:

sorted_persons = sorted( persons, key=lambda p:(p.age,descending(p.name)) )
© www.soinside.com 2019 - 2024. All rights reserved.