Reverse sort order for part of a key

I need to sort a list of objects. Currently I am doing it like this:

mylist = [aobject, bobject, cobject]
mylist.sort(key=mykey)
def mykey(sortelem):
    attribute1 = sortelem.attribute1
    attribute2 = sortelem.attribute2
    return (attribute1, attribute2)

Now I want to sort by attribute1 ascending, but attribute2 descending. I can always just do something like this:

return (attribute1, -1 * attribute2)

But that seems very unpythonic. I have searched the web, but it is a very hard thing to search for.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

TL;DR: You can sort twice, adding a reversed=True argument where needed. Sort by the tiebreaker first. This works because sorting is stable in Python.


Using - attribute2 instead of attribute2 inside the tuple is the easiest way if attribute2 is a number.

If attribute2 is a string, or something else that cannot be multiplied by -1 in a meaningful way, then this trick doesn’t work.

However, note that lst.sort and sorted are guaranteed to be stable sorts in python. In particular, this means that the two following code snippets are equivalent:

# SORT ACCORDING TO A TUPLE
l.sort(key=lambda x: (x[0], x[1]))

# SORT TWICE
l.sort(key=lambda x: x[1])
l.sort(key=lambda x: x[0])

Note how in the second version, we sorted by the tie-breaker first, then by the main criterion.

With the first version, we can sort in decreasing order of one of the two criterion by multiplying it by -1, if this criterion is numeric:

l.sort(key=lambda x: (x[0], -x[1]))

With the second version, however, we can sort in decreasing order of one of the two criterion, using optional argument reverse=True, and this works for numbers as well as for other types, for instance strings.

l.sort(key=lambda x: x[1], reverse=True)
l.sort(key=lambda x: x[0])

Finally, note that instead of defining a custom function with def or lambda to use as the sorting key, you can use operator.itemgetter or operator.attrgetter.

For instance, the following two code snippets are equivalent:

# FIRST VERSION
l.sort(key=lambda x: (x[0], x[1]))

# SECOND VERSION
from operator import itemgetter
l.sort(key=itemgetter(0, 1))

The following three code snippets are equivalent:

# FIRST VERSION
def mykey(x):
    x1 = x.attribute1
    x2 = x.attribute2
    return (x1, x2)
l.sort(key=mykey)

# SECOND VERSION
l.sort(key=lambda x: (x.attribute1, x.attribute2))

# THIRD VERSION
from operator import attrgetter
l.sort(key=attrgetter('attribute1', 'attribute2'))


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x