Документация Python неофициальный перевод

sorting.md

289 строк · 18.6 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.9/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()`](https://python-all.ru/3.9/library/stdtypes.html#list.sort) изменяет список на месте. Также есть встроенная функция [`sorted()`](https://python-all.ru/3.9/library/functions.html#sorted), которая создаёт новый отсортированный список из итерируемого объекта.1819В этом документе рассматриваются различные методы сортировки данных с помощью Python.2021## Основы сортировки2223Простая сортировка по возрастанию выполняется очень легко: достаточно вызвать функцию [`sorted()`](https://python-all.ru/3.9/library/functions.html#sorted). Она возвращает новый отсортированный список:2425```python26>>> sorted([5, 2, 3, 1, 4])27[1, 2, 3, 4, 5]28```2930Также можно использовать метод [`list.sort()`](https://python-all.ru/3.9/library/stdtypes.html#list.sort). Он изменяет список на месте (и возвращает `None`, чтобы избежать путаницы). Обычно он менее удобен, чем [`sorted()`](https://python-all.ru/3.9/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()`](https://python-all.ru/3.9/library/stdtypes.html#list.sort) определён только для списков. В то время как функция [`sorted()`](https://python-all.ru/3.9/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И [`list.sort()`](https://python-all.ru/3.9/library/stdtypes.html#list.sort), и [`sorted()`](https://python-all.ru/3.9/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`](https://python-all.ru/3.9/library/operator.html#module-operator) содержит функции [`itemgetter()`](https://python-all.ru/3.9/library/operator.html#operator.itemgetter), [`attrgetter()`](https://python-all.ru/3.9/library/operator.html#operator.attrgetter) и [`methodcaller()`](https://python-all.ru/3.9/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## По возрастанию и по убыванию126127Обе функции [`list.sort()`](https://python-all.ru/3.9/library/stdtypes.html#list.sort) и [`sorted()`](https://python-all.ru/3.9/library/functions.html#sorted) принимают параметр *reverse* с булевым значением. Он используется для указания сортировки по убыванию. Например, чтобы получить данные о студентах в обратном порядке по *возрасту*:128129```python130>>> sorted(student_tuples, key=itemgetter(2), reverse=True)131[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]132```133134```python135>>> sorted(student_objects, key=attrgetter('age'), reverse=True)136[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]137```138139## Стабильность сортировки и сложные сортировки140141Сортировки гарантированно являются [стабильными](https://python-all.ru/3.9/howto/sorting.html). Это означает, что когда несколько записей имеют один и тот же ключ, их исходный порядок сохраняется.142143```python144>>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]145>>> sorted(data, key=itemgetter(0))146[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]147```148149Обратите внимание, как две записи для *синего* сохраняют исходный порядок, так что `('blue', 1)` гарантированно предшествует `('blue', 2)`.150151Это замечательное свойство позволяет строить сложные сортировки в несколько этапов. Например, чтобы отсортировать данные о студентах сначала по убыванию *оценки*, а затем по возрастанию *возраста*, выполните сортировку по *возрасту* сначала, а затем снова отсортируйте по *оценке*:152153```python154>>> s = sorted(student_objects, key=attrgetter('age'))     # сортировка по вторичному ключу155>>> sorted(s, key=attrgetter('grade'), reverse=True)       # теперь сортировка по первичному ключу, по убыванию156[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]157```158159Это можно обобщить в функцию-обёртку, которая принимает список и кортежи из поля и порядка сортировки, чтобы выполнять сортировку в несколько проходов.160161```python162>>> def multisort(xs, specs):163...     for key, reverse in reversed(specs):164...         xs.sort(key=attrgetter(key), reverse=reverse)165...     return xs166```167168```python169>>> multisort(list(student_objects), (('grade', True), ('age', False)))170[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]171```172173Алгоритм [Timsort](https://python-all.ru/3.9/howto/sorting.html), используемый в Python, эффективно выполняет множественные сортировки, поскольку может использовать преимущества любого порядка, уже присутствующего в наборе данных.174175## Старый способ: Decorate-Sort-Undecorate176177Эта идиома называется Decorate-Sort-Undecorate по трём её шагам:178179- Сначала исходный список украшается новыми значениями, которые управляют порядком сортировки.180- Затем украшенный список сортируется.181- Наконец, украшения удаляются, создавая список, содержащий только исходные значения в новом порядке.182183Например, чтобы отсортировать данные о студентах по *оценке* с помощью подхода DSU:184185```python186>>> decorated = [(student.grade, i, student) for i, student in enumerate(student_objects)]187>>> decorated.sort()188>>> [student for grade, i, student in decorated]               # обратное преобразование189[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]190```191192Эта идиома работает, потому что кортежи сравниваются лексикографически: сравниваются первые элементы; если они одинаковы, то сравниваются вторые, и так далее.193194Не всегда строго необходимо включать индекс *i* в украшенный список, но его включение даёт два преимущества:195196- Сортировка стабильна – если два элемента имеют один и тот же ключ, их порядок сохранится в отсортированном списке.197- Исходные элементы не обязаны быть сравнимыми, поскольку порядок украшенных кортежей будет определяться максимум первыми двумя элементами. Так, например, исходный список может содержать комплексные числа, которые невозможно отсортировать напрямую.198199Другое название этой идиомы – [преобразование Шварца](https://python-all.ru/3.9/howto/sorting.html), по имени Рэндала Л. Шварца, который популяризировал её среди программистов на Perl.200201Теперь, когда сортировка в Python предоставляет ключевые функции, эта техника используется нечасто.202203## Старый способ с параметром *cmp*204205Многие конструкции, описанные в этом руководстве, предполагают Python 2.4 или новее. До этого не было встроенной функции [`sorted()`](https://python-all.ru/3.9/library/functions.html#sorted), а [`list.sort()`](https://python-all.ru/3.9/library/stdtypes.html#list.sort) не принимала именованных аргументов. Вместо этого все версии Py2.x поддерживали параметр *cmp* для обработки пользовательских функций сравнения.206207В Python 3.0 *cmp* параметр был полностью удалён (в рамках более масштабной работы по упрощению и унификации языка, устранению конфликта между расширенными сравнениями и `__cmp__()` магическим методом).208209В Py2.x метод sort принимал необязательную функцию, которую можно вызывать для выполнения сравнений. Эта функция должна была принимать два сравниваемых аргумента и возвращать отрицательное значение, если первый меньше второго, ноль, если они равны, или положительное значение, если первый больше второго. Например:210211```python212>>> def numeric_compare(x, y):213...     return x - y214>>> sorted([5, 2, 4, 1, 3], cmp=numeric_compare) 215[1, 2, 3, 4, 5]216```217218Или можно изменить порядок сравнения с помощью:219220```python221>>> def reverse_numeric(x, y):222...     return y - x223>>> sorted([5, 2, 4, 1, 3], cmp=reverse_numeric) 224[5, 4, 3, 2, 1]225```226227При переносе кода с Python 2.x на 3.x может возникнуть ситуация, когда пользователь предоставляет функцию сравнения, и её нужно преобразовать в ключевую функцию. Следующая обёртка упрощает эту задачу:228229```python230def cmp_to_key(mycmp):231    'Convert a cmp= function into a key= function'232    class K:233        def __init__(self, obj, *args):234            self.obj = obj235        def __lt__(self, other):236            return mycmp(self.obj, other.obj) < 0237        def __gt__(self, other):238            return mycmp(self.obj, other.obj) > 0239        def __eq__(self, other):240            return mycmp(self.obj, other.obj) == 0241        def __le__(self, other):242            return mycmp(self.obj, other.obj) <= 0243        def __ge__(self, other):244            return mycmp(self.obj, other.obj) >= 0245        def __ne__(self, other):246            return mycmp(self.obj, other.obj) != 0247    return K248```249250Чтобы преобразовать в ключевую функцию, просто оберните старую функцию сравнения:251252```pycon253>>> sorted([5, 2, 4, 1, 3], key=cmp_to_key(reverse_numeric))254[5, 4, 3, 2, 1]255```256257В Python 3.2 функция [`functools.cmp_to_key()`](https://python-all.ru/3.9/library/functools.html#functools.cmp_to_key) была добавлена в модуль [`functools`](https://python-all.ru/3.9/library/functools.html#module-functools) стандартной библиотеки.258259## Разное260261- Для сортировки с учётом локали используйте [`locale.strxfrm()`](https://python-all.ru/3.9/library/locale.html#locale.strxfrm) в качестве ключевой функции или [`locale.strcoll()`](https://python-all.ru/3.9/library/locale.html#locale.strcoll) в качестве функции сравнения.262- Параметр *reverse* сохраняет стабильность сортировки (так что записи с равными ключами сохраняют исходный порядок). Интересно, что этот эффект можно имитировать без параметра, дважды применив встроенную функцию [`reversed()`](https://python-all.ru/3.9/library/functions.html#reversed):263264  ```python265  >>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]266  >>> standard_way = sorted(data, key=itemgetter(0), reverse=True)267  >>> double_reversed = list(reversed(sorted(reversed(data), key=itemgetter(0))))268  >>> assert standard_way == double_reversed269  >>> standard_way270  [('red', 1), ('red', 2), ('blue', 1), ('blue', 2)]271  ```272- Процедуры сортировки используют `<` при сравнении двух объектов. Поэтому легко добавить стандартный порядок сортировки в класс, определив метод [`__lt__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__lt__):273274  ```python275  >>> Student.__lt__ = lambda self, other: self.age < other.age276  >>> sorted(student_objects)277  [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]278  ```279280  Однако обратите внимание, что `<` может вернуться к использованию [`__gt__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__gt__), если [`__lt__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__lt__) не реализован (см. [`object.__lt__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__lt__)).281- Ключевые функции не обязаны напрямую зависеть от сортируемых объектов. Ключевая функция может также обращаться к внешним ресурсам. Например, если оценки студентов хранятся в словаре, их можно использовать для сортировки отдельного списка имён студентов:282283  ```python284  >>> students = ['dave', 'john', 'jane']285  >>> newgrades = {'john': 'F', 'jane':'A', 'dave': 'C'}286  >>> sorted(students, key=newgrades.__getitem__)287  ['jane', 'dave', 'john']288  ```289