Сортировки и лямбда-функции

Параметр key стандартной сортировки

У стандартной сортировки (метода sort списка, функции sorted) есть более универсальный способ задания порядка сортировки при помощи параметра key. Значение этого параметра  некоторая функция, которая вызывается для каждого элемента, перед сравнением этих элементов: элементы списка сортируются, но сравниваются не значения элементов, а результат вызова переданной функции от этого элемента.

Например, пусть дан список строк, содержащих цифры, нужно упорядочить элементы списка, сравнивая их, как числа, а не как строки. Это можно сделать так:

a = ["10", "20", "30", "1", "2", "3", "111", "112", "222"]
a.sort(key=int)

В качестве параметра передаётся функция int, которая будет использоваться для сравнения элементов, которое теперь будет выполняться в виде int(a[i]) < int(a[j]). Вместо функции int можно использовать любую другую функцию, которая принимает один аргумент (элемент массива) и возвращает значение, например, если вызвать a.sort(key=len), то строки будут упорядочены по длине.

В сложных случаях функцию нужно написать самостоятельно, например, пусть дан список чисел, который нужно упорядочить по последней цифре. Напишем функцию, которая возвращает последнюю цифру числа:

def last_digit(n):
    return n % 10

a.sort(key=last_digit)

Параметр key можно использовать вместе с параметром reverse.

Лямбда-функции

В предыдущем примере пришлось создавать отдельную функцию только для того, чтобы задать порядок сортировки, что захламляет программу ненужными функциями. В таких случаях нужно использовать лямбда-функции: “одноразовые фукцнии, которые можно объявлять без использовать слова def, прямо при вызове сортировки. Лямбда-функция — это функция, которая состоит только из одной строки с инструкцией return, то есть функция сразу возвращает значение по набору аргументов. Лямбда-функции объявляются таким образом:

lambda список переменных-аргументов: возвращаемое значение

Например, отсортировать список чисел по последней цифре можно при помощи следующей лямбда-функции:

a = [3, 15, 22, 13, 12, 32, 45, 43]
a.sort(key=lambda n: n % 10)

Рассмотрим другой пример. Пусть дан список точек, каждая точка: кортеж из двух чисел. Например, [(3, -2), (7, 1), (0, 4)]. Этот список нужно отсортировать по возрастанию расстояния от начала координат до точки. Напишем лямбда-функцию:

a.sort(key=lamda point: point[0] ** 2 + point[1] ** 2)

Элементами списка являются кортежи из двух координат, можно обратиться к этим координатам по индексам [0] и [1].

Устойчивость сортировки

Вернёмся к примеру сортировки по последней цифре. В приведённом выше примере упорядоченный список будет таким:

[22, 12, 32, 3, 13, 43, 15, 45]

Этот пример иллюстрирует свойство устойчивости сортировки: функция сортировки не переставлят элементы, если они равны друг другу. В данном случае функция упорядочивает числа по последней цифре, а при равной последней цифре сохраняется порядок следования элементов в исходном списке: 22, 12, 32.

Что делать, если нужно сделать сложную сортировку, учитывающую несколько критериев? Например, при равной последней цифре нужно упорядочить элементы в порядке возрастания самих чисел.

Первый способ решения: напишем функцию, которая будет возвращать кортеж из двух чисел: последней цифры и самого числа. Кортежи сравниваются в лексикографическом порядке, поэтому при равенстве остатка от деления будут сравниваться сами числа.

a.sort(key=lambda n: (n % 10, n))

Второй способ: воспользуемся устойчивостью сортировки. Отстортируем список сначала по возрастанию чисел, а затем — по последней цифре. Тогда при равном значении последней цифры сохранится ранее полученный порядок.

a.sort()
a.sort(key=lambda n: n % 10)

То есть сортировку по \(k\) параметрам (если по первому параметру элементы равны, то сравнить по второму, если равны два параметра, то сравнить по третьему и т.д.) можно заменить на \(k\) последовательных сортировок, выполняющихся в обратном порядке (от наименее значимого параметра к наиболее значимому).

Функция operator.itemgetter

При сортировке кортежей частой задачей является сортировка по какому-то одному элементу кортежа. Например, если нужно отсортировать кортежи по элементу с индексом 1, то можно написать такую лямбда-функцию:

a.sort(key=lambda elem: elem[1])

Для удобства в модуле operator есть функция itemgetter, которая позволяет создавать подобные функции, а именно, функция реализована примерно так:

def itemgetter(i):
    return lambda elem: elem[i]

То есть operator.itemgetter(i) — это функция, при вызове которой от числового параметра создаётся лямдба-функция, которую можно использовать в качестве параметра key. Если вызвать функцию itemgetter от нескольких параметров, то полученная функция будет возвращать кортеж из элементов с заданными индексами.