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

huangapple go评论79阅读模式
英文:

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) -&gt; str:
        return f&quot;({self.name}, {self.age})&quot;

and a List of Person

persons = [
    Person(&quot;Bob&quot;, 25),
    Person(&quot;Alice&quot;, 25),
    Person(&quot;Charlie&quot;, 23),
    Person(&quot;Dave&quot;, 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 &lt; p2.name:
        return 1
    elif p1.name &gt; 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,用于sortsorted,你本可以在这里使用它,但在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 == 1False - 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 &gt; b) - (a &lt; b)

if &gt; and &lt; 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&lt;b.name)-(b.name&lt;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 &gt; other.value

usage:

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

huangapple
  • 本文由 发表于 2023年7月28日 04:06:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76783085.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定