Сортировка списков в Python — метод sort и функция sorted

В Python для сортировки списков предусмотрен метод sort, также можно использовать встроенную в Python функцию sorted. Основная разница между ними заключается в том, что метод списка сортирует сам список, к которому применяется (говорят, сортировка происходит на месте), а функция sorted возвращает новый список, оставляя оригинал неизменным.

>>> a = [1, -2, 0]
>>> b = [10, -20, 3]
>>> a.sort()
>>> c = sorted(b)
>>> a
[-2, 0, 1]
>>> b
[10, -20, 3]
>>> c
[-20, 3, 10]

Как sort, так и sorted принимают аргумент True или False по ключу reverse. Значение False задано по-умолчанию, поэтому сортировка происходит от меньшего к большему, то есть по возрастанию. Для обратной сортировки, то есть по убыванию, параметру reverse следует присвоить значение True.

>>> a = [4, 2, 5, 3]
>>> a.sort(reverse=True)
>>> a
[5, 4, 3, 2]

Кроме этого sort и sorted имеют параметр key, которому присваивается функция. Допустим, у нас есть матрица, которую мы хотим отсортировать:

>>> a = [[1, 10], [0, 15], [-1, 12], [3, 9]]
>>> a.sort()
>>> a
[[-1, 12], [0, 15], [1, 10], [3, 9]]

По-умолчанию сортировка выполняется по первым элементам вложенных списков, то есть в случае матрицы по первому столбцу. Чтобы изменить принцип сортировки, надо в метод sort() передать значение для параметра key. Этим значением должна быть другая функция, которая будет автоматически вызываться на каждый очередной элемент списка (он будет в нее передаваться). Она может сделать с ним что угодно и вернуть что угодно. По этому "что угодно" и происходит сортировка.

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

Конечно, мы можем определить обычную функцию (кстати, в Python таковая, если содержит всего одно выражение в теле, может быть записана в одну строку):

>>> def num_item(i): return i[1]
>>> a.sort(key=num_item)
>>> a
[[3, 9], [1, 10], [-1, 12], [0, 15]]

Однако будет проще с lambda-выражением:

>>> a.sort()
>>> a
[[-1, 12], [0, 15], [1, 10], [3, 9]]
>>> a.sort(key=lambda i: i[1])
>>> a
[[3, 9], [1, 10], [-1, 12], [0, 15]]

При этом key не обязательно используется только при наличии вложенных списков.

>>> a = [1, 2, 3, 4, 5, 6]
>>> sorted(a, key=lambda i: i%2)
[2, 4, 6, 1, 3, 5]

Здесь мы отсортировали так, что сначала идут четные элементы, потом нечетные.

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

В примере выше, если применить lambda-функцию к каждому элементу списка-оригинала, то получится такой список-маска: [1, 0, 1, 0, 1, 0]. Он и будет отсортирован. Однако в список-результат вместо значений из маски будут подставлены связанные с ними значения из списка-оригинала.

Тот факт, что у нас внутри себя отсортированы как четные (2, 4, 6), так и нечетные (1, 3, 5) числа, никак не связан с выполненной сортировкой. Это связано только с тем, как элементы были заданы в исходном списке. Сравните:

>>> a = [6, 4, 1, 5, 3, 2]
>>> sorted(a, key=lambda i: i%2)
[6, 4, 2, 1, 5, 3]

Параметру key можно присвоить и встроенную в Python функцию.

>>> s = ['bb', 'd', 'cccc', 'aaa']
>>> sorted(s)
['aaa', 'bb', 'cccc', 'd']
>>> sorted(s, key=len)
['d', 'bb', 'aaa', 'cccc']
>>> sorted(s, key=len, reverse=True)
['cccc', 'aaa', 'bb', 'd']

Здесь мы сортируем строки, которые по-умолчанию сортируются лексикографически. Однако используя встроенную функцию len, которая в данном случае возвращает длину строки, мы можем сортировать в зависимости от длины строк.

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

def sort_col(item):
    return item[int(n)]


a = [['Петя', 10, 130, 35], ['Вася', 11, 135, 39],
     ['Женя', 9, 120, 33], ['Дима', 10, 128, 30]]

n = input('Сортировать по имени(0), '
          'возрасту(1), росту(2), весу(3): ')

b = sorted(a, key=sort_col)

for i in b:
    print('%7s %3d %4d %3d' % (i[0], i[1], i[2], i[3]))

Если выбрать сортировку по весу, вывод будет таким:

Сортировать по имени(0), возрасту(1), росту(2), весу(3): 3
   Дима  10  128  30
   Женя   9  120  33
   Петя  10  130  35
   Вася  11  135  39

Отдельно посмотрим на определение функции sort_col:

def sort_col(item):
    return item[int(n)]

Она принимает некий объект item, извлекает из него элемент под номером n и возвращает этот элемент.

При использовании sort_col в качестве значения параметра key функции sorted происходит вызов sort_col по отношению к каждому элементу списка. В данном случае элементом списка является вложенный список условного формата [имя, возраст, рост, вес], он передается в функцию. Функция sort_col извлекает из него элемент под заданным номером n и передает этот элемент функции sorted. В свою очередь sorted сортирует элементы исходного списка, исходя из соответствующих значений, которые вернул sort_col.

Здесь вместо обычной можно использовать lambda-функцию:

b = sorted(a, key=lambda item: item[int(n)])

Однако если требуются более сложная обработка данных без полноценной функции не обойтись.

Практическая работа

  1. Доработайте приведенную в уроке программу про юных спортсменов таким образом, чтобы пользователь выбирал не только столбец сортировки, также мог указать тип сортировки – по возрастанию или убыванию.
  2. Дан список строк. Отсортируйте его лексикографически по последним буквам слов.