Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: How to sort a list of custom objects by multiple attributes by different order?

For example, if I have a Person class

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?

like image 520
maplemaple Avatar asked Mar 11 '26 22:03

maplemaple


2 Answers

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)]
like image 124
Andrej Kesely Avatar answered Mar 14 '26 12:03

Andrej Kesely


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)
like image 29
Kenneth Granahan Avatar answered Mar 14 '26 10:03

Kenneth Granahan