例如,如果我有一个
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 的方式来处理这个问题。有什么建议吗?
这段代码应该可以做到。我运行了它,输出看起来是正确的
sorted_persons = sorted(persons, key=lambda p: (-p.age, p.name), reverse=True)
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
。
另一种解决方案:
为了排序,您可以定义自定义
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)]
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)) )