Sorting with key functions and cmp_to_key

functools · Essentials · 8 min

Some orderings are naturally pairwise comparisons, and sorting is where Python enforces key functions.

Python's sorted, list.sort, min, max and heapq order items with a key function: a one-argument function computing a sort key for each item, such as key=str.lower or key=lambda u: (u.last, u.first). Each key is computed once, which is efficient.

Some orderings, though, are easier to define by comparing two items: return a negative number if a comes first, positive if b comes first, zero if equal. Python 2 accepted such cmp functions directly; Python 3 doesn't. functools.cmp_to_key(cmp) wraps a comparison function into a key:

sorted(items, key=cmp_to_key(compare))

It works by creating wrapper objects whose < calls your function. Each comparison is a Python call, so it's slower than a key function; prefer a key when you can express one.

Classic cases for cmp_to_key:

  • Arranging numbers to form the largest concatenation: compare a+b with b+a.
  • Comparisons from external libraries, such as locale.strcoll for locale-aware string ordering.
  • Custom version ordering with complex tie-breaking rules.

Remember that Python's sort is stable, so you can sort by a secondary key first and then by the primary key, as an alternative to complex comparisons.

Example

from functools import cmp_to_key

def bigger_concat(a, b):
    return (a + b < b + a) - (a + b > b + a)   # -1, 0 or 1

nums = ["3", "30", "34", "5", "9"]
print("".join(sorted(nums, key=cmp_to_key(bigger_concat))))  # 9534330

users = [("ann", 31), ("bob", 25), ("cy", 31)]
print(sorted(users, key=lambda u: (-u[1], u[0])))
# [('ann', 31), ('cy', 31), ('bob', 25)]

def by_length_then_alpha(a, b):
    if len(a) != len(b):
        return len(a) - len(b)
    return (a > b) - (a < b)
print(sorted(["pear", "fig", "apple", "kiwi"], key=cmp_to_key(by_length_then_alpha)))
# ['fig', 'kiwi', 'pear', 'apple']

Remember

  • Sorting uses one-argument key functions in Python 3
  • cmp_to_key adapts a pairwise comparison (negative/zero/positive)
  • Prefer a key function when possible; it's faster

Watch out for

  • Passing a comparison function directly as key=, which compares by the function's return of one argument and fails.

Check yourself

  1. A comparison function cmp(a, b) should return a negative number when...

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/functools.html#functools.cmp_to_key