英文:
Python: How to sort a list of custom objects by multiple attributes by different order?
问题
以下是翻译好的内容:
例如,如果我有一个 `Person` 类
```python
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
降序排序。然而,我觉得应该有一种更清晰、更符合Python风格的方法来处理这个问题。有什么建议吗?
<details>
<summary>英文:</summary>
For example, if I have a `Person` class
```python
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})"
and a List of Person
persons = [
Person("Bob", 25),
Person("Alice", 25),
Person("Charlie", 23),
Person("Dave", 25),
]
I can sort the list by age
in ascending order, and in case of a tie, sort by name
in ascending order using the following method:
sorted_persons = sorted(persons, key=lambda p: (p.age, p.name))
Question:
However, I'm looking for a way to sort the list by age
in ascending order and, in the event of a tie in age, sort by name
in descending order. How could I achieve this in Python?
I've come up with one solution, as shown below, but it seems a bit inelegant. Is there a more succinct way to write a string comparison method that can handle all three cases (i.e., less than, equal to, and greater than)? For instance, Java has a s1.compareTo(s2)
method that makes such comparisons straightforward.
Here's the solution I'm currently working with:
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))
This code correctly sorts the persons
list first by age
in ascending order, and then by name
in descending order when the ages are equal. However, I feel there should be a cleaner, more Pythonic way to handle this. Any suggestions?
答案1
得分: 4
另一种解决方案:
为了进行排序,您可以定义自定义的 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)]
英文:
Another solution:
For the purpose of sorting you can define custom str
class where you override the __lt__
magic method (less than):
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)
Prints:
[(Charlie, 23), (Dave, 25), (Bob, 25), (Alice, 25)]
答案2
得分: 2
这段代码应该可以实现它。我运行了它,输出看起来正确。
sorted_persons = sorted(persons, key=lambda p: (-p.age, p.name), reverse=True)
英文:
This snippet of code should do it. I ran it and the output looks right
sorted_persons = sorted(persons, key=lambda p: (-p.age, p.name), reverse=True)
答案3
得分: 2
Python以前曾经有一个名为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
。
英文:
Python used to have the cmp
built-in function which did what you want.
There was also a keyword argument callled cmp
for sort
and sorted
, which is what you could have used here, but it was removed in Python 3.0+ because the key
was often more performant than cmp
. Unfortunately you found the one where it quite isn't.
However there is a trick you could do here: you can use tuple comparison and you can swap the names, therefore:
def compare(p1, p2):
a = p1.age, p2.name
b = p2.age, p1.name
then you can just compare these two. There is also a trick to nicely produce -1, 0, 1 by using the fact that True and False are integers with values of 1 and 0 respectively:
return (a > b) - (a < b)
if >
and <
are normally defined then maximum one side will result in True, with the other being False; True - False == 1
and False - True == -1
, and False - False == 0
.
答案4
得分: 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)))
英文:
Python's sort is stable. This means that the order of items that have the same sort key is preserved in the resulting list.
You can leverage this by sorting in steps, starting with the least significant order:
sorted_persons = sorted(persons, key=lambda p: p.name, reverse=True)
sorted_persons.sort(key=lambda p: p.age)
You could also streamline the cmp_to_key to a lambda (but that's not a different solution):
sorted_persons = sorted(persons,key=cmp_to_key(
lambda a,b: a.age-b.age or (a.name<b.name)-(b.name<a.name)))
For a more general solution, you could create a class that will handle all descending key ordering expressed directly in the key=lambda's return tuples:
class descending():
def __init__(self,value): self.value = value
def __lt__(self,other): return self.value > other.value
usage:
sorted_persons = sorted( persons, key=lambda p:(p.age,descending(p.name)) )
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论