How to write sort key functions for descending values?

The slow-but-elegant way to do this is to create a value wrapper that has reversed ordering:

from functools import total_ordering
@total_ordering
class ReversedOrder:
    def __init__(self, value):
        self.value = value
    def __eq__(self, other):
        return other.value == self.value
    def __lt__(self, other):
        return other.value < self.value

If you don't have functools.total_ordering, you'd have to implement all 6 comparisons, e.g.:

import operator
class ReversedOrder:
    def __init__(self, value):
        self.value = value
for x in ['__lt__', '__le__', '__eq__', '__ne__', '__ge__', '__gt__']:
    op = getattr(operator, x)
    setattr(ReversedOrder, x, lambda self, other, op=op: op(other.value, self.value))

I think the docs are incomplete. I interpret the word "primarily" to mean that there are still reasons to use cmp_to_key, and this is one of them. cmp was removed because it was an "attractive nuisance:" people would gravitate to it, even though key was a better choice.

But your case is clearly better as a cmp function, so use cmp_to_key to implement it.


The most generic way to do this is simply to sort separately by each key in turn. Python's sorting is always stable so it is safe to do this:

sort(data, key=tiebreakerkey)
sort(data, key=datekey, reverse=True)

will (assuming the relevant definitions for the key functions) give you the data sorted by descending date and ascending tiebreakers.

Note that doing it this way is slower than producing a single composite key function because you will end up doing two complete sorts, so if you can produce a composite key that will be better, but splitting it out into separate sorts gives a lot of flexibility: given a key function for each column you can make any combination of them and specify reverse for any individual column.

For a completely generic option:

keys = [ (datekey, True), (tiebreakerkey, False) ]
for key, rev in reversed(keys):
    sort(data, key=key, reverse=rev)

and for completeness, though I really think it should be avoided where possible:

from functools import cmp_to_key
sort(data, key=cmp_to_key(your_old_comparison_function))

The reason I think you should avoid this you go back to having n log n calls to the comparison function compared with n calls to the key function (or 2n calls when you do the sorts twice).

Tags:

Python

Sorting