sorting.md
1> **Источник:** https://python-all.ru/2.7/howto/sorting.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Руководство по сортировке89**Автор**1011Andrew Dalke and Raymond Hettinger1213**Версия**14150.11617Встроенный метод `list.sort()` изменяет список на месте. Также есть встроенная функция [`sorted()`](https://python-all.ru/2.7/library/functions.html#sorted), которая создаёт новый отсортированный список из итерируемого объекта.1819В этом документе рассматриваются различные методы сортировки данных с помощью Python.2021## Основы сортировки2223Простая сортировка по возрастанию выполняется очень легко: достаточно вызвать функцию [`sorted()`](https://python-all.ru/2.7/library/functions.html#sorted). Она возвращает новый отсортированный список:2425```python26>>> sorted([5, 2, 3, 1, 4])27[1, 2, 3, 4, 5]28```2930Можно также использовать метод `list.sort()` списка. Он изменяет список на месте (и возвращает `None`, чтобы избежать путаницы). Обычно это менее удобно, чем [`sorted()`](https://python-all.ru/2.7/library/functions.html#sorted), но если исходный список не нужен, это немного эффективнее.3132```python33>>> a = [5, 2, 3, 1, 4]34>>> a.sort()35>>> a36[1, 2, 3, 4, 5]37```3839Ещё одно различие: метод `list.sort()` определён только для списков. В то время как функция [`sorted()`](https://python-all.ru/2.7/library/functions.html#sorted) принимает любой итерируемый объект.4041```python42>>> sorted({1: 'D', 2: 'B', 3: 'B', 4: 'E', 5: 'A'})43[1, 2, 3, 4, 5]44```4546## Ключевые функции4748Начиная с Python 2.4, обе функции `list.sort()` и [`sorted()`](https://python-all.ru/2.7/library/functions.html#sorted) получили параметр *key*, указывающий функцию, которая будет вызвана для каждого элемента списка перед проведением сравнений.4950Например, вот сравнение строк без учёта регистра:5152```python53>>> sorted("This is a test string from Andrew".split(), key=str.lower)54['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']55```5657Значением параметра *key* должна быть функция, принимающая один аргумент и возвращающая ключ для сортировки. Этот метод эффективен, поскольку функция ключа вызывается ровно один раз для каждой записи на входе.5859Часто используется сортировка сложных объектов по некоторым их индексам. Например:6061```python62>>> student_tuples = [63... ('john', 'A', 15),64... ('jane', 'B', 12),65... ('dave', 'B', 10),66... ]67>>> sorted(student_tuples, key=lambda student: student[2]) # сортировка по возрасту68[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]69```7071Тот же подход работает для объектов с именованными атрибутами. Например:7273```python74>>> class Student:75... def __init__(self, name, grade, age):76... self.name = name77... self.grade = grade78... self.age = age79... def __repr__(self):80... return repr((self.name, self.grade, self.age))81```8283```python84>>> student_objects = [85... Student('john', 'A', 15),86... Student('jane', 'B', 12),87... Student('dave', 'B', 10),88... ]89>>> sorted(student_objects, key=lambda student: student.age) # сортировка по возрасту90[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]91```9293## Функции модуля operator9495Описанные выше шаблоны ключевых функций очень распространены, поэтому Python предоставляет вспомогательные функции, чтобы сделать функции доступа проще и быстрее. Модуль operator содержит [`operator.itemgetter()`](https://python-all.ru/2.7/library/operator.html#operator.itemgetter), [`operator.attrgetter()`](https://python-all.ru/2.7/library/operator.html#operator.attrgetter), а начиная с Python 2.5 – функцию [`operator.methodcaller()`](https://python-all.ru/2.7/library/operator.html#operator.methodcaller).9697Использование этих функций делает приведённые выше примеры проще и быстрее:9899```python100>>> from operator import itemgetter, attrgetter101```102103```python104>>> sorted(student_tuples, key=itemgetter(2))105[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]106```107108```python109>>> sorted(student_objects, key=attrgetter('age'))110[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]111```112113Функции модуля operator позволяют выполнять многоуровневую сортировку. Например, сортировка сначала по *оценке*, а затем по *возрасту*:114115```python116>>> sorted(student_tuples, key=itemgetter(1,2))117[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]118```119120```python121>>> sorted(student_objects, key=attrgetter('grade', 'age'))122[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]123```124125Функция [`operator.methodcaller()`](https://python-all.ru/2.7/library/operator.html#operator.methodcaller) осуществляет вызовы методов с фиксированными параметрами для каждого сортируемого объекта. Например, метод [`str.count()`](https://python-all.ru/2.7/library/stdtypes.html#str.count) можно использовать для вычисления приоритета сообщения, подсчитывая количество восклицательных знаков в сообщении:126127```python128>>> from operator import methodcaller129>>> messages = ['critical!!!', 'hurry!', 'standby', 'immediate!!']130>>> sorted(messages, key=methodcaller('count', '!'))131['standby', 'hurry!', 'immediate!!', 'critical!!!']132```133134## По возрастанию и по убыванию135136Обе функции `list.sort()` и [`sorted()`](https://python-all.ru/2.7/library/functions.html#sorted) принимают параметр *reverse* с булевым значением. Он используется для указания сортировки по убыванию. Например, чтобы получить данные о студентах в обратном порядке по *возрасту*:137138```python139>>> sorted(student_tuples, key=itemgetter(2), reverse=True)140[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]141```142143```python144>>> sorted(student_objects, key=attrgetter('age'), reverse=True)145[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]146```147148## Стабильность сортировки и сложные сортировки149150Начиная с Python 2.2, гарантируется, что сортировки являются [устойчивыми](https://python-all.ru/2.7/howto/sorting.html). Это означает, что когда несколько записей имеют одинаковый ключ, их исходный порядок сохраняется.151152```python153>>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]154>>> sorted(data, key=itemgetter(0))155[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]156```157158Обратите внимание, как две записи для *синего* сохраняют исходный порядок, так что `('blue', 1)` гарантированно предшествует `('blue', 2)`.159160Это замечательное свойство позволяет строить сложные сортировки в несколько этапов. Например, чтобы отсортировать данные о студентах сначала по убыванию *оценки*, а затем по возрастанию *возраста*, выполните сортировку по *возрасту* сначала, а затем снова отсортируйте по *оценке*:161162```python163>>> s = sorted(student_objects, key=attrgetter('age')) # сортировка по вторичному ключу164>>> sorted(s, key=attrgetter('grade'), reverse=True) # теперь сортировка по первичному ключу, по убыванию165[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]166```167168Алгоритм [Timsort](https://python-all.ru/2.7/howto/sorting.html), используемый в Python, эффективно выполняет множественные сортировки, поскольку может использовать преимущества любого порядка, уже присутствующего в наборе данных.169170## Старый способ: Decorate-Sort-Undecorate171172Эта идиома называется Decorate-Sort-Undecorate по трём её шагам:173174- Сначала исходный список украшается новыми значениями, которые управляют порядком сортировки.175- Затем украшенный список сортируется.176- Наконец, украшения удаляются, создавая список, содержащий только исходные значения в новом порядке.177178Например, чтобы отсортировать данные о студентах по *оценке* с помощью подхода DSU:179180```python181>>> decorated = [(student.grade, i, student) for i, student in enumerate(student_objects)]182>>> decorated.sort()183>>> [student for grade, i, student in decorated] # обратное преобразование184[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]185```186187Эта идиома работает, потому что кортежи сравниваются лексикографически: сравниваются первые элементы; если они одинаковы, то сравниваются вторые, и так далее.188189Не всегда строго необходимо включать индекс *i* в украшенный список, но его включение даёт два преимущества:190191- Сортировка стабильна – если два элемента имеют один и тот же ключ, их порядок сохранится в отсортированном списке.192- Исходные элементы не обязаны быть сравнимыми, поскольку порядок украшенных кортежей будет определяться максимум первыми двумя элементами. Так, например, исходный список может содержать комплексные числа, которые невозможно отсортировать напрямую.193194Другое название этой идиомы – [преобразование Шварца](https://python-all.ru/2.7/howto/sorting.html), по имени Рэндала Л. Шварца, который популяризировал её среди программистов на Perl.195196Для больших списков и списков, где информация для сравнения дорого вычисляется, а также для версий Python до 2.4, DSU, вероятно, является самым быстрым способом сортировки списка. Для версий 2.4 и новее ключевые функции предоставляют ту же функциональность.197198## Старый способ с параметром *cmp*199200Многие конструкции, описанные в этом руководстве, предполагают Python 2.4 или новее. До этого не было встроенной функции [`sorted()`](https://python-all.ru/2.7/library/functions.html#sorted), а `list.sort()` не принимала именованных аргументов. Вместо этого все версии Py2.x поддерживали параметр *cmp* для обработки пользовательских функций сравнения.201202В Python 3 параметр *cmp* был полностью удалён (в рамках более масштабной работы по упрощению и унификации языка, устранению конфликта между расширенными сравнениями и магическим методом [`__cmp__()`](https://python-all.ru/2.7/reference/datamodel.html#object.__cmp__)).203204В Python 2 функция `sort()` допускала необязательную функцию, которая могла вызываться для выполнения сравнений. Эта функция должна принимать два аргумента для сравнения, а затем возвращать отрицательное значение, если первый меньше второго, ноль, если они равны, или положительное значение, если первый больше второго. Например:205206```python207>>> def numeric_compare(x, y):208... return x - y209>>> sorted([5, 2, 4, 1, 3], cmp=numeric_compare) 210[1, 2, 3, 4, 5]211```212213Или можно изменить порядок сравнения с помощью:214215```python216>>> def reverse_numeric(x, y):217... return y - x218>>> sorted([5, 2, 4, 1, 3], cmp=reverse_numeric) 219[5, 4, 3, 2, 1]220```221222При переносе кода с Python 2.x на 3.x может возникнуть ситуация, когда пользователь предоставляет функцию сравнения, и её нужно преобразовать в ключевую функцию. Следующая обёртка упрощает эту задачу:223224```python225def cmp_to_key(mycmp):226 'Convert a cmp= function into a key= function'227 class K(object):228 def __init__(self, obj, *args):229 self.obj = obj230 def __lt__(self, other):231 return mycmp(self.obj, other.obj) < 0232 def __gt__(self, other):233 return mycmp(self.obj, other.obj) > 0234 def __eq__(self, other):235 return mycmp(self.obj, other.obj) == 0236 def __le__(self, other):237 return mycmp(self.obj, other.obj) <= 0238 def __ge__(self, other):239 return mycmp(self.obj, other.obj) >= 0240 def __ne__(self, other):241 return mycmp(self.obj, other.obj) != 0242 return K243```244245Чтобы преобразовать в ключевую функцию, просто оберните старую функцию сравнения:246247```pycon248>>> sorted([5, 2, 4, 1, 3], key=cmp_to_key(reverse_numeric))249[5, 4, 3, 2, 1]250```251252В Python 2.7 функция [`functools.cmp_to_key()`](https://python-all.ru/2.7/library/functools.html#functools.cmp_to_key) была добавлена в модуль functools.253254## Разное255256- Для сортировки с учётом локали используйте [`locale.strxfrm()`](https://python-all.ru/2.7/library/locale.html#locale.strxfrm) в качестве ключевой функции или [`locale.strcoll()`](https://python-all.ru/2.7/library/locale.html#locale.strcoll) в качестве функции сравнения.257- Параметр *reverse* по-прежнему сохраняет устойчивость сортировки (так что записи с одинаковыми ключами сохраняют свой исходный порядок). Интересно, что этот эффект можно воспроизвести без этого параметра, дважды применив встроенную функцию [`reversed()`](https://python-all.ru/2.7/library/functions.html#reversed):258259 ```python260 >>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]261 >>> standard_way = sorted(data, key=itemgetter(0), reverse=True)262 >>> double_reversed = list(reversed(sorted(reversed(data), key=itemgetter(0))))263 >>> assert standard_way == double_reversed264 >>> standard_way265 [('red', 1), ('red', 2), ('blue', 1), ('blue', 2)]266 ```267- Чтобы создать стандартный порядок сортировки для класса, достаточно добавить соответствующие методы расширенных сравнений:268269 ```python270 >>> Student.__eq__ = lambda self, other: self.age == other.age271 >>> Student.__ne__ = lambda self, other: self.age != other.age272 >>> Student.__lt__ = lambda self, other: self.age < other.age273 >>> Student.__le__ = lambda self, other: self.age <= other.age274 >>> Student.__gt__ = lambda self, other: self.age > other.age275 >>> Student.__ge__ = lambda self, other: self.age >= other.age276 >>> sorted(student_objects)277 [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]278 ```279280 Для сравнений общего назначения рекомендуется определить все шесть операторов расширенных сравнений. Декоратор класса [`functools.total_ordering()`](https://python-all.ru/2.7/library/functools.html#functools.total_ordering) упрощает реализацию.281- Ключевые функции не обязаны напрямую зависеть от сортируемых объектов. Ключевая функция может также обращаться к внешним ресурсам. Например, если оценки студентов хранятся в словаре, их можно использовать для сортировки отдельного списка имён студентов:282283 ```python284 >>> students = ['dave', 'john', 'jane']285 >>> grades = {'john': 'F', 'jane':'A', 'dave': 'C'}286 >>> sorted(students, key=grades.__getitem__)287 ['jane', 'dave', 'john']288 ```289