Модуль 5 · Урок 5.4
Сортировка и выбор лучших элементов
sorted и sort, ключ сортировки, несколько критериев, max с ключом и почему сортировка не доказывает жадный выбор.
Таблица результатов олимпиады: сначала по убыванию балла, при равном балле — по алфавиту. Слова — по длине. Строки с числами — как числа, а не как текст. Сортировка сравнивает не сами элементы, а их ключи: любой порядок задаётся функцией-ключом, а несколько критериев — кортежем ключей. Ключ — это функция, которую sorted вызывает для каждого элемента, как в уроке о функции как значении.
Сортировать будем слова ["арбуз", "кот", "дом", "окно"] и баллы учеников: по длине, по нескольким критериям, а лучшего выберем без полной сортировки.
sorted и sort
sorted возвращает новый список, а sort меняет список на месте.
a = [8, 2, 5]b = sorted(a)print(a)print(b)a.sort(reverse=True)print(a)sorted принимает перебираемый источник и возвращает новый список. .sort() доступен у списка, меняет его на месте и возвращает None. Параметр reverse=True задаёт обратный порядок.
Строки из цифр сортируются как текст: "100" идёт раньше "20", потому что сначала сравниваются первые символы. Если нужен числовой порядок, преобразуй данные в числа либо задай числовой ключ.
print(sorted(["20", "100", "3"]))print(sorted(["20", "100", "3"], key=int))Ключ сортировки
Ключ сортировки — значение, по которому сравнивают элемент; сам элемент не меняется.
words = ["арбуз", "кот", "дом", "окно"]print(sorted(words, key=len))Передаётся функция len, а не результат len(...). При сортировке Python применяет функцию ключа к каждому элементу и использует полученные значения для упорядочивания. Посмотри по шагам, как это происходит:
- Исходный список. sorted сначала вычисляет ключ каждого элемента.
- Ключ слова «арбуз»: len(«арбуз») = 5.
- Ключ слова «кот»: len(«кот») = 3.
- Ключ слова «дом»: len(«дом») = 3.
- Ключ слова «окно»: len(«окно») = 4.
- Слова встали в порядке ключей. Слова с равным ключом — «кот», «дом» — сохранили исходный порядок.
Ключ решает всё: меняется ключ — меняется порядок, а сами слова остаются теми же. Переключай правила и сравни результаты:
lambda w: w[-1] — последняя буква: «з» раньше «м», «о» и «т».Несколько критериев
Несколько критериев задают кортежем: кортежи сравниваются слева направо. Кортежи сравниваются лексикографически: сначала первые элементы; если они равны — вторые, и так далее. Это похоже на порядок слов в словаре, но применяется к любым сравнимым элементам.
students = [("Анна", 91), ("Илья", 84), ("Марк", 91)]ordered = sorted( students, key=lambda student: (-student[1], student[0]))print(ordered)Балл сортируется по убыванию благодаря минусу: 91 превращается в −91, что меньше −84. При равных баллах имя сортируется по возрастанию. reverse=True для всего кортежа развернул бы оба критерия, поэтому это не то же самое. Как кортежи ключей решают порядок — по шагам:
- Исходные записи: имя и балл.
- Ключ каждой записи — кортеж: минус балл и имя.
- Сначала сравниваются первые элементы: −91 меньше остальных, поэтому Анна и Марк будут впереди.
- У Анна и Марк первые элементы равны — решает второй: имена сравниваются по алфавиту.
- Итог: по убыванию балла, при равном балле — по алфавиту.
Устойчивость и max
Сортировка устойчива, а один лучший элемент находят без сортировки. Сортировка Python устойчива: элементы с одинаковым ключом сохраняют взаимный исходный порядок. Например, слова "кот" и "дом" имеют одинаковую длину и остались в исходной последовательности.
Если нужен только лучший элемент, нет необходимости сортировать всё:
words = ["арбуз", "кот", "дом", "окно"]longest = max(words, key=len)print(longest)print(min([], default=None))max проходит элементы один раз и помнит лучший; при одинаковом ключе лучшим остаётся первый. Для возможно пустого источника можно указать default=None. Как max выбирает — по шагам:
- «кот»: длина 3 — первый элемент, пока лучший.
- «слон»: 4 > 3 — новый лучший.
- «мышь»: 4 не больше 4 — лучшим остаётся «слон».
- «ёж»: 2 < 4 — лучшим остаётся «слон».
- Список кончился: max вернул «слон».
Сквозная задача курса получает продолжение: не одно наибольшее, а два. Для [5, -2, 10, 7] выражение sorted(numbers, reverse=True)[:2] даёт [10, 7] — сортировка по убыванию и срез первых двух.
Жадный выбор
Сортировка по одному показателю не доказывает, что жадный выбор верен. Выбор «самого выгодного сейчас» называют жадным алгоритмом. Иногда его правильность можно доказать. Например, если нужно купить максимум предметов при ограниченном бюджете и все цены неотрицательны, выгодно брать самые дешёвые: любой набор из k предметов стоит не меньше суммы k минимальных цен.
prices = [50, 20, 70, 10, 40]budget = 100count = 0spent = 0for price in sorted(prices): if spent + price > budget: break spent += price count += 1print(count, spent)- Цены по возрастанию: выгоднее всего брать самые дешёвые.
- Пробуем 10: 0 + 10 = 10 — помещается.
- Купили 10: потрачено 10.
- Пробуем 20: 10 + 20 = 30 — помещается.
- Купили 20: потрачено 30.
- Пробуем 40: 30 + 40 = 70 — помещается.
- Купили 40: потрачено 70.
- 70 + 50 = 120 > 100: не помещается — break.
- Итог: 3 предмета за 70.
Самые дешёвые 10, 20 и 40 стоят 70; следующая цена 50 уже не помещается. Никакие четыре предмета не уложатся в 100: четыре самых дешёвых стоят 120.
Но сортировка по одному показателю не решает произвольный выбор оптимального набора. Если у предметов разные цены и полезности, выбор по цене или отношению полезности к цене для неделимых предметов может оказаться неверным. Сначала доказательство подхода, затем код.
Итог
Любой порядок задаётся ключом, а несколько критериев — кортежем ключей. Вот что понадобится дальше — и в задачах ЕГЭ с сортировкой.
sorted и sortsorted(x)создаёт новый список,x.sort()меняетxи возвращаетNone;reverse=True— по убыванию.key=функцияkey=функциясортирует по значению функции; передаётся функция без скобок.- Несколько критериевНесколько критериев — кортеж ключей; минус перед числом даёт убывание только этого критерия.
- Лучший без сортировкиСортировка устойчива; один лучший элемент —
max(x, key=…), пустой источник —default=. - Жадный выборЖадный выбор после сортировки верен только с доказательством.
Проверьте себя
Ответь сначала сам, потом открой ответ.
Что дадут sorted(["10", "9", "100"]) и sorted(["10", "9", "100"], key=int)?
ОтветСкрыть ответ
['10', '100', '9'] и ['9', '10', '100'].
Чему равно b после a = [3, 1, 2] и b = a.sort()? Чему равно a?
ОтветСкрыть ответ
b = None, a = [1, 2, 3].
Отсортируй [("Ира", 80), ("Аня", 92), ("Боря", 80)] по убыванию балла, а при равенстве — по имени.
ОтветСкрыть ответ
[('Аня', 92), ('Боря', 80), ('Ира', 80)].
Что вернёт max(["кот", "слон", "мышь"], key=len) и почему?
ОтветСкрыть ответ
слон: у «слон» и «мышь» длина 4, max оставляет первый.
Чем sorted(students, key=lambda s: (-s[1], s[0])) отличается от sorted(students, key=lambda s: (s[1], s[0]), reverse=True) для учеников с равными баллами?
ОтветСкрыть ответ
Во втором случае имена при равных баллах идут в обратном алфавитном порядке.
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Что напечатает программа?
numbers = [31, 22, 13, 42, 11]print(sorted(numbers, key=lambda n: (n % 10, n)))Запишите список так, как его печатает Python.
Подсказка
Ключ — кортеж: сначала последняя цифра, при равенстве — само число.
Разбор
Последние цифры: 1 у 11 и 31, 2 у 22 и 42, 3 у 13.
Ответ: [11, 31, 22, 42, 13]
Напиши программу: сколько предметов на бюджет
Цены [30, 5, 25, 15, 60], бюджет 50. Напиши программу, которая покупает самые дешёвые предметы, пока хватает денег, и печатает количество и потраченную сумму. Запусти её и запиши, что она напечатала.
Запишите два числа через пробел: количество, затем потраченную сумму.
Подсказка
Перебирай sorted(prices) и останови цикл, когда следующая цена не помещается в бюджет.
Разбор
Например:
prices = [30, 5, 25, 15, 60]budget = 50count = 0spent = 0for price in sorted(prices): if spent + price > budget: break spent += price count += 1print(count, spent)5 + 15 + 25 = 45; следующая цена 30 уже не помещается.
Ответ: 3 45
Напиши программу: сортировка по сумме цифр
Напиши программу, которая сортирует [15, 7, 22, 9, 30, 4] по сумме цифр, а при равенстве — по самому числу, и печатает результат. Запусти её и запиши, что она напечатала.
Запишите список так, как его печатает Python.
Подсказка
Ключ — кортеж (сумма цифр, число); сумму цифр удобно взять функцией.
Разбор
Например:
def digit_sum(n): total = 0 while n > 0: total += n % 10 n //= 10 return total print(sorted([15, 7, 22, 9, 30, 4], key=lambda n: (digit_sum(n), n)))Суммы цифр: 30 — 3, 4 — 4, 22 — 4, 15 — 6, 7 — 7, 9 — 9.
Ответ: [30, 4, 22, 15, 7, 9]