Custom key function in Python3

Viewed 80

I want to write a custom key function for a question I saw at Pramp. I am used to using something like:

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

This time I need a more complicated key function, and I realized that I don't know how to write it. How can I write such a key function for sorting in Python3? Which value should key function return? What should its input be?

My key function (not working)

def key_function(num1, num2):
  if abs(num1) < abs(num2):
    return num1
  if abs(num1) > abs(num2):
    return num2
  if num1 < num2:
    return num1
  if num1 > num2:
    return num2
  if num1 == num2:
    return num1

From Pramp:

If two numbers have the same absolute value, sort them according to sign, where the negative numbers come before the positive numbers.

input:  arr = [2, -7, -2, -2, 0]
output: [0, -2, -2, 2, -7]

Their pseudo code answer:

    def compare(a, b):
        if abs(a) < abs(b): return -1
        if abs(a) > abs(b): return 1
        if a < b: return -1
        if a > b: return 1
        return 0

    arr.sort(cmp = compare)
    return arr
2 Answers

The key function needs to return a tuple with absolute values and a sortable indicator of the sign of the value.

  • abs() will give the absolute value
  • value >= 0 would do for something to sort by sign; negative values produce False, which sorts before True.

Translated to a lambda that’s:

my_list.sort(key=lambda v: (abs(v), v >= 0))

The key function produces:

 2 => (2, True)
-7 => (7, False)
-2 => (2, False)
 0 => (0, True)

which Python then uses to sort the list by. Since tuples are compared element by element and the first non-equal value determines their order, that means the False / True sign flag only is used when the first element is equal, e.g. when comparing (2, False) (-2) with (2, True) (2).

Just for the sake of completeness, I'd like to point out that overwriting the comparison method on a custom object is another way of customising a sort operation.

If the custom key function becomes too complex to handle, this can be a worthwhile approach.

Citing from the docs of functools.total_ordering:

Given a class defining one or more rich comparison ordering methods, this class decorator supplies the rest. This simplifies the effort involved in specifying all of the possible rich comparison operations:

The class must define one of __lt__(), __le__(), __gt__(), or __ge__(). In addition, the class should supply an __eq__() method.

For example:

@total_ordering
class Student:
    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))
Related