The Python Oracle

Using a comparator function to sort

This video explains
Using a comparator function to sort

--

Become part of the top 3% of the developers by applying to Toptal
https://topt.al/25cXVn

--

Music by Eric Matyas
https://www.soundimage.org
Track title: Puzzle Game 2 Looping

--

Chapters
00:00 Question
01:03 Accepted answer (Score 58)
01:47 Answer 2 (Score 53)
02:52 Answer 3 (Score 3)
03:13 Answer 4 (Score 0)
04:32 Thank you

--

Full question
https://stackoverflow.com/questions/1274...

Answer 1 links:
[According to the docs]: https://docs.python.org/3/library/functi...
[@Fred Foo's answer]: https://stackoverflow.com/a/12749495/122...

Answer 2 links:
[answer of @kaya3]: https://stackoverflow.com/a/60159044/102...

Answer 3 links:
[the answer of @Tuan Chau]: https://stackoverflow.com/a/70074078/329...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #sorting #comparator

#avk47



ANSWER 1

Score 72


In Python 3 there is no cmp argument for the sorted function (nor for list.sort).

According to the docs, the signature is now sorted(iterable, *, key=None, reverse=False), so you have to use a key function to do a custom sort. The docs suggest:

Use functools.cmp_to_key() to convert an old-style cmp function to a key function.

Here's an example:

>>> def compare(x, y):
...     return x[0] - y[0]
... 
>>> data = [(4, None), (3, None), (2, None), (1, None)]
>>> from functools import cmp_to_key
>>> sorted(data, key=cmp_to_key(compare))
[(1, None), (2, None), (3, None), (4, None)]

However, your function doesn't conform to the old cmp function protocol either, since it returns True or False. For your specific situation you can do:

>>> your_key = cmp_to_key(make_comparator(cmpValue))
>>> sorted(data, key=your_key)
[(1, None), (2, None), (3, None), (4, None)]

using the make_comparator function from @Fred Foo's answer.




ACCEPTED ANSWER

Score 65


You're passing the comparator as the key function. You should be passing it as the cmp, wrapped in some kind of function that turns it into a proper comparator.

def make_comparator(less_than):
    def compare(x, y):
        if less_than(x, y):
            return -1
        elif less_than(y, x):
            return 1
        else:
            return 0
    return compare

sortedDict = sorted(subjects, cmp=make_comparator(cmpValue), reverse=True)

(Although actually, you should be using key functions:

sorted(subjects, operator.itemgetter(0), reverse=True)

Also note that sortedDict will not actually be a dict, so the name is rather confusing.)




ANSWER 3

Score 5


The answer of @kaya3 is correct. I just propose another implementation in which we can use boolean for the comparator.

class YourTupleComparator(tuple):
    def __lt__(self, other):
        return self[0] < other[0]

sorted(subjects, key=YourTupleComparator)



ANSWER 4

Score 1


I don't know how does the answer of @Tuan Chau work. However, it may fail in complex situations.

Consider this comparison: each element is a tuple with two dimension. Element A is less than B if one of two conditions met: 1. A[0] is less than B[0]. 2. A[0]==B[0] and A[1]>B[0]. Therefore (1, 2) < (2, 1), (1, 2) < (1, 1).

Try the following snippets:

from functools import cmp_to_key

class Character(tuple):
    def __le__(self, other) -> bool:
        if self[0] < other[0] or (self[0] == other[0] and self[1] >= other[1]):
            return True
        return False


def compare(x, y):
    if x[0] == y[0]:
        return y[1] - x[1]
    return x[0] - y[0]


if __name__ == "__main__":
    array = [[1, 1], [2, 1], [2, 2], [1, 2]]
    print(sorted(array, key=Character))  # [[1, 1], [1, 2], [2, 1], [2, 2]]

    print(sorted(array, key=cmp_to_key(compare)))  # [[1, 2], [1, 1], [2, 2], [2, 1]]

As you can see, the result of class Character is wrong.