0

In Python 3, sort() has parameters key and reverse. If you're using a custom compare function, and it sorts on multiple items (per object), how do you individually do ascending or descending per item? In my case, the items are not numeric, so negating a value is not an option.

I don't see any syntactic way to do what I want, so I'm stuck. In Python 2, you could do it very easily, because the compare function returned -1, 0, +1. Python 3 seems less flexible.

Selcuk
  • 57,004
  • 12
  • 102
  • 110
Hays Jedi
  • 9
  • 3
  • Please refer to [this answer](https://stackoverflow.com/a/56842689/6890912) rather than the accepted answer in the duplicate question for a generalized solution. – blhsing Mar 09 '23 at 01:32

2 Answers2

1

You would use the functools.cmp_to_key() function:

Transform an old-style comparison function to a key function. Used with tools that accept key functions (such as sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby()). This function is primarily used as a transition tool for programs being converted from Python 2 which supported the use of comparison functions.

That is, you can convert the following Python 2 code:

mylist.sort(cmp=my_comparator_function)

to Python 3 as follows

from functools import cmp_to_key

mylist.sort(key=cmp_to_key(my_comparator_function))
Selcuk
  • 57,004
  • 12
  • 102
  • 110
-2

This is not a good approach. I will not remove the answer because the discussion in comments is valuable.

You can overwrite the dunder method __lt__ in the object to stipulate how comparison is made. Since you set the rule of comparison, you can sort however you want and don't have to supply key to the sorting function:

class ObjectToCompare:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __lt__(self, other):
        if self.a < other.a:
            return True
        if self.b > other.b:
            return True

    def __repr__(self):
        # this is just for printing purpose
        # it has nothing to do with sorting
        return f'({self.a}, {self.b})'


lst = [
    ObjectToCompare(2, 'a'),
    ObjectToCompare(2, 'b'),
    ObjectToCompare(1, 'a'),
    ObjectToCompare(1, 'c'),
]

print(sorted(lst))

# output:
# [(1, c), (1, a), (2, b), (2, a)]

Instances of Foo are sorted ascending on a first. If a (integer) is the same it is sorted descending on b (string). This logic is expressed in the __lt__ method.

user4157124
  • 2,809
  • 13
  • 27
  • 42
Fanchen Bao
  • 3,310
  • 1
  • 21
  • 34
  • This approach would make `sorted` return a list of `Foo` objects, not a list of tuples. Overriding `__repr__` just for printing is not what the OP wants. – blhsing Mar 09 '23 at 01:35
  • Overwriting `__repr__` is just for printing purpose. It has nothing to do with sorting or OP's request. In addition, OP does not mention the sorting object is a tuple. – Fanchen Bao Mar 09 '23 at 01:47
  • What I mean is that, you're basically writing a wrapper class around objects you actually want to compare. But rather than sorting the intended objects, you end up sorting instances of the wrapper class. Unless you go through an extra step of converting the resulting list of instances of the wrapper class back to the original objects, this is not what doing what the OP wants, i.e. getting objects themselves sorted by a particular order. – blhsing Mar 09 '23 at 01:59
  • My intention is to show OP that he/she can add `__lt__` directly into his/her original object, not wrapping the original object in yet another object. But I see your point. This approach won't work if OP does not have direct access to the original object. I will make edits to the answer. – Fanchen Bao Mar 09 '23 at 02:34
  • 1
    But then that means whenever the user wants to sort the objects in a different way (perhaps via a friendly GUI menu), the `__lt__` method of the class needs to be overridden to provide that output. Not a clean approach in my opinion. The `key` argument is offered exactly for this purpose, and we should best utilize it instead. – blhsing Mar 09 '23 at 03:19
  • The `__lt__` approach can save some key strokes in sorting, but it has a pretty major limitation as discussed above. That said, if the comparison rule of the object is pretty much set in stone, and you can make peace with the limitation, and you can provide thorough documentation on the default comparison rule, I have no objection of using the `__lt__` method. – Fanchen Bao Mar 10 '23 at 08:35