📌 Больше полезных статей по информатике и программированию вы можете найти у нас на сайте.
📢 Следить за новостями мира информатики, а также общаться, делиться впечатлениями и готовиться к экзаменам лучше вместе, в нашем Telegram-канале.
Сортировка данных
Один из фундаментальных процессов в обработке информации – это сортировка. Представьте себе, что вы пытаетесь найти определенную книгу в библиотеке, где тысячи различных литературных произведений расставлены в абсолютно случайном порядке. Такая задача может занять часы!
Однако, если все книги будут упорядочены по алфавиту или тематическим разделам, поиск займет всего несколько минут. Этот простой пример иллюстрирует ключевую ценность сортировки: она превращает хаос в структуру, делая работу с данными эффективной и осмысленной.
В нашей повседневной жизни мы постоянно сталкиваемся с отсортированными данными: списки контактов в телефоне, упорядоченные по алфавиту; транзакции в банковском приложении, отсортированные по дате; товары в интернет-магазине, ранжированные по количеству отзывов или цене.
Сортировка применяется практически везде, где требуется каким-либо образом систематизировать информацию для облегчения поиска, анализа или представления данных.
В мире программирования сортировка имеет еще более широкое применение:
- В аналитике данных — для выявления трендов, обнаружения выбросов или подготовки данных к визуализации
- В поисковых системах — для ранжирования результатов по релевантности
- В базах данных — для оптимизации запросов и индексирования
- В разработке игр — для определения порядка отрисовки объектов или создания рейтинговых таблиц
- В финансовых приложениях — для ранжирования транзакций, инвестиций или потенциальных рисков
В Python, как в одном из самых популярных языков для работы с данными, уже встроен функционал для сортировки данных. В этой статье мы сосредоточимся на изучении этих встроенных в Python функций, которые являются основой для большинства задач сортировки.
Мы научимся работать с ключевыми параметрами из этих функций, а также на практике применять лямбда-выражения для сортировки сложных структур данных.
После прочтения этой статьи вы не только научитесь эффективно применять функции сортировки в своих проектах, но и глубже поймете принципы работы с данными в Python в целом. Давайте погрузимся в мир сортировки и раскроем всю мощь этого фундаментального инструмента программирования.
Функции сортировки
Для сортировки данных в Python по умолчанию встроена функция sorted() и метод списков sort(). Давайте разберёмся, чем они отличаются, и как правильно их применять.
Функция sorted()
Начнём с функции sorted(). Она позволяет упорядочить элементы какой-либо коллекции. Данная функция возвращает отсортированную в порядке возрастания копию исходной коллекции в виде списка.
Функция sorted() имеет следующий синтаксис:
Где:
- iterable — коллекция, которую нужно отсортировать (список, кортеж, строка, словарь, множество и т.д.)
- key — необязательный аргумент, функция, которая применяется к каждому элементу перед сравнением (определяет критерий сортировки)
- reverse — необязательный логический параметр, определяющий порядок сортировки (по умолчанию False, что означает сортировку по возрастанию)
Давайте продемонстрируем её работу с различными типами коллекций. Начнём с самого базового типа – со списка. Отсортируем список целых чисел в порядке возрастания.
С кортежами и множествами все будет аналогично – на выходе получим отсортированный по возрастанию список целых чисел. Но совсем иначе обстоит работа сортировки со строками.
Символы в строке будут отсортированы в лексикографическом порядке, то есть в порядке их следования в Unicode (сначала идут спецсимволы, затем – заглавные латинские буквы, после них – несколько знаков препинания, строчные буквы и все остальные символы).
Для примера отсортируем строку случайных символов: «ZV-bc_aA».
На выходе мы получим список символов, которые расставлены в соответствии с их кодом в Unicode: «[‘-‘, ‘A’, ‘V’, ‘Z’, ‘_’, ‘a’, ‘b’, ‘c’]».
Если вам нужна именно отсортированная строка, а не список, то можно объединить все элементы списка в строку с помощью метода join():
Но куда интереснее обстоит дело с сортировкой словарей. По умолчанию функция sorted() позволяет сортировать словарь только по ключам. Например, у нас в словаре ключами являются уникальные идентификаторы (ID) пользователей, а значениями – их имена.
Отсортируем словарь по возрастанию значения ключей:
Да, это не совсем то, что мы ожидали увидеть после фразы «отсортируем словарь». Но это поведение вполне очевидно – функция sorted() возвращает только отсортированный список. При работе со словарями нам необходимо применять эту функцию для сортировки ключей или значений словаря, а затем создавать новый словарь, но уже с нужным порядком следования элементов.
Совсем скоро мы научимся этому, но для начала нам необходимо разобраться с методом sort() и с использованием параметра key для более продвинутой сортировки.
Метод sort()
В отличие от функции sorted(), метод sort() является методом списков в Python, что и означает его главное отличие — он работает исключительно со списками и не может быть применен к другим итерируемым объектам, таким как кортежи, строки или словари.
Ключевая особенность метода sort() состоит в том, что он выполняет сортировку «на месте». Это означает, что метод изменяет исходный список вместо создания нового отсортированного списка.
После вызова метода sort() оригинальный список будет переупорядочен согласно указанным критериям, а метод вернет None. Данное поведение представляет собой фундаментальное различие с функцией sorted(), которая всегда возвращает новый список и сохраняет исходный объект неизменным.
Давайте рассмотрим работу этого метода на все том же примере с сортировкой списка целых чисел. Обратите внимание, что теперь мы не сохраняем результат вызова этого метода в отдельную переменную, как раньше это делали с «sorted_list». Нам достаточно просто применить этот метод к нашему списку.
Когда же стоит использовать метод sort() вместо функции sorted()? Этот метод предпочтителен в следующих ситуациях:
- Когда вам не нужно сохранять оригинальную последовательность элементов списка, и вы хотите работать с тем же объектом списка.
- Когда важна эффективность использования памяти — sort() более экономичен с точки зрения памяти, поскольку не создает дополнительную копию списка, что особенно важно при работе с большими наборами данных.
- Когда сортировка является промежуточным шагом в обработке данных, и вы продолжите работать с тем же списком.
- Когда вы хотите упростить код, избегая создания дополнительных переменных для хранения отсортированного списка.
Параметры key и reverse
Переходим к работе с параметрами функции sorted(). Аналогичные будут применимы и для метода sort(), но мы будем работать именно с sorted(), ввиду того, что далее будет часть примеров, в которых мы будем сортировать словари.
Начнём с самого простого – параметр reverse со значением True позволяет «развернуть» направление сортировки. То есть если изначально сортировка идёт по возрастанию, то при передаче аргумента «reverse=True» функция будет сортировать элементы в порядке убывания.
Для примера отсортируем список целых чисел в порядке убывания:
Далее уже посложнее. Параметр key принимает функцию, которая применяется к каждому элементу коллекции перед сравнением. Такая функция еще называется «функция-ключ».
Наглядно работу функции, передаваемой в key, можно рассмотреть на примере сортировки списка по длине строк, входящих в него. Для этого нужно передать в key функцию len() (обратите внимание, что передаётся только название функции, без скобок!):
Что же здесь произошло? К исходному списку была применена функция len(), которая высчитала количество символов в каждой строке и вернула это количество в виде целых чисел. Можно сказать, что из такого списка «[‘шаг’, ‘в’, ‘будущее’]» мы получили такой: «[3, 1, 7]».
А дальше произошла обычная сортировка целых чисел. То есть из списка «[3, 1, 7]» был получен список «[1, 3, 7]». А для формирования итогового списка строк эти числовые значения были обратно «заменены» на строки: «[‘в’, ‘шаг’, ‘будущее’]».
Рассмотрим еще один пример сортировки с применением функции-ключа. Представим, что нам нужно отсортировать список так, чтобы сначала шли по возрастанию все чётные числа, а только потом нечётные.
Так, а разве у нас есть функция для определения чётности числа? Такой встроенной функции нет, но мы же можем создать лямбда-выражение, результатом которого как раз и будет проверка чётности числа. Вспомним, что такое лямбда-выражения в Python и как они работают.
Лямбда-выражения при сортировке
Лямбда-выражения в Python — это компактный способ создания небольших одноразовых функций «на лету», без необходимости их формального определения с использованием ключевого слова def.
Они представляют собой встроенный механизм функционального программирования, который особенно полезен, когда нам нужна простая функция для кратковременного использования, например, в качестве аргумента для функций высшего порядка, таких как sorted(), map(), filter() и других.
Базовый синтаксис лямбда-выражения выглядит следующим образом:
Где:
- lambda — ключевое слово, обозначающее начало анонимной (лямбда) функции
- аргументы — список аргументов, разделенных запятыми (может быть пустым)
- выражение — одиночное выражение, результат которого будет возвращен функцией
Помните, что лямбда-выражение ограничено единственным выражением — в нём нельзя использовать несколько строк кода, операторы ветвления или циклы в их стандартном синтаксисе!
Сравним работу обычной именованной функции для определения чётности числа и лямбда-выражения с тем же функционалом:
Теперь вернёмся к нашей задаче по сортировке чисел по чётности и возрастанию. Нам нужно создать функцию-ключ, которая будет возвращать кортеж из двух элементов: первый элемент определяет приоритет чётности (0 для четных, 1 для нечетных), а второй элемент — само число для сортировки в пределах своей группы.
Для определения приоритета чётности числа «x» нам достаточно выражения «x % 2». Для чётных оно будет возвращать ноль (остаток от деления на два будет равен нулю), а для нечётных – единицу.
Вторым элементом кортежа у нас будет само число «x». Кстати, если требуется отсортировать в обратном порядке, то вторым элементом кортежа можно поставить значение «–x».
Получим такую программу для сортировки списка чисел:
Теперь вернёмся к сортировке словарей. Важно понимать, что словари в Python до версии 3.7 были неупорядоченными коллекциями, а начиная с Python 3.7 словари сохраняют порядок вставки элементов. Так что обращайте внимание на используемую версию интерпретатора Python при работе со словарями!
Сам по себе словарь не может быть отсортирован по значениям ключей или значений. Давайте рассмотрим, как можно создать новые отсортированные словари с помощью функции sorted().
Пусть у нас есть словарь, в котором ключами являются имена учеников, а значениями – их баллы за экзамен. Давайте для начала отсортируем этот словарь по именам в алфавитном порядке.
В этом примере мы используем генератор словаря и функцию sorted() для создания нового словаря, где ключи упорядочены по алфавиту.
Теперь отсортируем этот же словарь по убыванию баллов за экзамен, то есть по убыванию значений этого словаря.
Здесь мы используем метод items() для получения пар «ключ, значение», сортируем их по значению с помощью lambda-функции, а затем преобразуем результат обратно в словарь.
До текущего момента мы не сталкивались с ситуацией, когда в сортируемой последовательности встречаются одинаковые значения. Допустим, у нас есть список с кортежами под каждого ученика: в кортеже содержится имя ученика, его оценка и очерёдность сдачи работы.
Мы хотим, чтобы в итоговом рейтинге учитывалась не только оценка ученика, но и скорость сдачи работы. Например, если два ученика получили за работу оценку 5, то на первом месте будет именно тот, кто сдал эту работу раньше.
Пусть мы получали сданные работы в таком порядке:
- Алиса с оценкой 4
- Борис с оценкой 5
- Виктор с оценкой 4
- Галина с оценкой 3
- Дмитрий с оценкой 5
- Елена с оценкой 3
В коде это можно выразить таким списком кортежей:
На «отлично» работу сдали двое: Борис и Дмитрий. Но Борис сдал работу раньше, чему и соответствует позиция его кортежа в списке students (второй элемент).
Как мы видим, в отсортированном списке сохранился исходный порядок следования элементов: Борис сдал работу раньше Дмитрия, Алиса – раньше Виктора и т.д.
Это говорит нам о стабильности сортировки в Python. Стабильность сортировки — это свойство алгоритма сортировки, означающее, что порядок равных элементов сохраняется после сортировки.
То есть если у нас есть два элемента А и Б с одинаковым значением по ключу сортировки, и А стоял перед Б в исходной последовательности, то после стабильной сортировки А все еще будет стоять перед Б.
Стабильность сортировки критически важна во многих приложениях, особенно при:
- Многоуровневой сортировке — когда данные сортируются последовательно по нескольким критериям.
- Сохранении контекстной информации — когда относительный порядок элементов содержит дополнительную информацию, которую важно сохранить.
- Работе с пользовательскими интерфейсами — когда пользователи ожидают предсказуемого поведения при сортировке таблиц или списков.
Стабильность сортировки особенно полезна при выполнении многоуровневой сортировки в несколько этапов.
Давайте расширим наш рейтинг учеников. Теперь в списке students будут храниться словари следующего формата:
- Имя ученика (ключ name)
- Класс ученика (ключ class)
- Оценка за работу (ключ grade)
Имеем такой список словарей:
Сначала отсортируем его по оценке:
Теперь отсортируем этот список по классу. То есть сначала будут ученики 11 «А» класса, затем – 11 «Б».
Здесь благодаря стабильности сортировки, студенты в каждом классе остаются отсортированными по оценке после второго этапа сортировки.
Однако есть более компактный способ добиться того же самого эффекта. В современном Python часто вместо последовательной сортировки используется сортировка по кортежу критериев — это гарантирует правильный порядок применения критериев:
Здесь мы сначала сортируем по классу – сначала 11 «А», потом 11 «Б» – затем по оценке внутри каждого класса.
Для облегчения работы со словарями можно применять методы модуля operator, такие как itemgetter(), attrgetter() и methodcaller(). Но их применению мы уделим время уже в следующих статьях.