Веду на сто
Учебная статья

Python: сортировка и группировка записей

sort, sorted, ключи с разными направлениями и словарь групп для обработки записей задания №26.

В файле задания №26 одна запись обычно описывает объект несколькими полями. Сортировка меняет порядок записей, а группировка собирает вместе записи с одинаковым выбранным признаком. Эти операции выполняют разные действия: порядок по номеру ряда ещё не превращает данные в отдельные списки мест каждого ряда.

Изменение списка и новая копия

Метод a.sort() переставляет элементы списка a. Функция sorted(a) возвращает новый отсортированный список, сохраняя прежний. По умолчанию порядок возрастающий; параметр reverse=True задаёт убывание.

a = [7, 2, 7, 4]
b = sorted(a)
print(a)
print(b)
a.sort(reverse=True)
print(a)

Результаты: [7, 2, 7, 4], [2, 4, 7, 7], [7, 7, 4, 2]. Повторяющиеся значения сохранены: два груза одинаковой массы остаются двумя грузами. Превращение списка в множество перед сортировкой удалило бы один из них.

Запись a = a.sort() ошибочна для этой цели: метод возвращает None, и имя a больше не ссылается на список. Самостоятельная строка a.sort() уже выполняет нужное изменение.

Ключ задаёт сравниваемые поля

Ключ сортировки — значение, вычисляемое для каждой записи и используемое при сравнении. Пусть запись хранит начало и конец мероприятия. Для обработки по времени окончания ключ должен брать второе поле, а не первое.

rows = [(1, 9), (3, 5)]
rows.sort(
    key=lambda x: x[1]
)
print(rows)

Получится [(3, 5), (1, 9)]. Выражение lambda x: x[1] задаёт короткую функцию: получает запись x и возвращает её поле с индексом один. Поля внутри записи не переставляются; меняются местами целые записи.

Несколько критериев

Пусть учебные записи содержат балл и ID. Нужно расположить их по баллу вниз, а при равном балле — по ID вверх. Ключ из двух чисел сравнивается сначала по первому, затем, только при равенстве, по второму.

rows = [(80, 9), (90, 5),
        (90, 2)]
rows.sort(
    key=lambda x: (
        -x[0], x[1]
    )
)
print(rows)

Порядок будет [(90, 2), (90, 5), (80, 9)]. Минус меняет направление только для балла. Параметр reverse=True развернул бы сравнение всего ключа и поставил бы больший ID раньше меньшего. Поэтому он не заменяет разные направления отдельных числовых полей.

Числовые поля заранее преобразуются через int(). Иначе строки "12" и "5" сравниваются по символам: первая начинается с меньшей цифры и оказывается раньше, хотя число двенадцать больше пяти. Нужный порядок определяется типом данных, а не только выбранным полем.

Сортировка Python устойчива. При равном балле сортировка только по баллу сохраняет входной порядок, а не гарантирует возрастание ID. Требуемый дополнительный критерий должен быть задан явно.

Словарь отдельных групп

Словарь связывает ключ со значением. Здесь ключ — номер ряда, значение — список занятых мест в нём. Сначала создаются группы, затем сортируется каждый список.

rows = [(2, 8), (1, 4),
        (2, 3)]
groups = {}
for row, seat in rows:
    if row not in groups:
        groups[row] = []
    groups[row].append(seat)
for row in groups:
    groups[row].sort()
print(groups[2])

Печатается [3, 8]. Поля row и seat получают значения очередной пары. Проверка наличия ключа создаёт пустой список только при первом появлении ряда; повторное создание потеряло бы ранее добавленные места.

Порядок ключей словаря отражает их добавление, поэтому для обхода рядов по возрастанию используется for row in sorted(groups):. Если условие объединяет кресла одного номера в разных рядах, ключом становится номер кресла, а значениями — ряды. Ось группировки выбирается по смыслу условия.

Какие поля определяют порядок и что должно объединяться в одну группу? После этого остаётся обосновать сам выбор объектов: сортировка не доказывает, что полученный набор оптимален.