К содержанию

Модуль 5 · Урок 5.4

Сортировка и выбор лучших элементов

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

3 задачи

Таблица результатов олимпиады: сначала по убыванию балла, при равном балле — по алфавиту. Слова — по длине. Строки с числами — как числа, а не как текст. Сортировка сравнивает не сами элементы, а их ключи: любой порядок задаётся функцией-ключом, а несколько критериев — кортежем ключей. Ключ — это функция, которую sorted вызывает для каждого элемента, как в уроке о функции как значении.

Сортировать будем слова ["арбуз", "кот", "дом", "окно"] и баллы учеников: по длине, по нескольким критериям, а лучшего выберем без полной сортировки.

sorted и sort

sorted возвращает новый список, а sort меняет список на месте.

a = [8, 2, 5]b = sorted(a)print(a)print(b)a.sort(reverse=True)print(a)
[8, 2, 5] [2, 5, 8] [8, 5, 2]

sorted принимает перебираемый источник и возвращает новый список. .sort() доступен у списка, меняет его на месте и возвращает None. Параметр reverse=True задаёт обратный порядок.

Строки из цифр сортируются как текст: "100" идёт раньше "20", потому что сначала сравниваются первые символы. Если нужен числовой порядок, преобразуй данные в числа либо задай числовой ключ.

print(sorted(["20", "100", "3"]))print(sorted(["20", "100", "3"], key=int))
['100', '20', '3'] ['3', '20', '100']

Ключ сортировки

Ключ сортировки — значение, по которому сравнивают элемент; сам элемент не меняется.

words = ["арбуз", "кот", "дом", "окно"]print(sorted(words, key=len))
['кот', 'дом', 'окно', 'арбуз']

Передаётся функция len, а не результат len(...). При сортировке Python применяет функцию ключа к каждому элементу и использует полученные значения для упорядочивания. Посмотри по шагам, как это происходит:

  1. Исходный список. sorted сначала вычисляет ключ каждого элемента.
  2. Ключ слова «арбуз»: len(«арбуз») = 5.
  3. Ключ слова «кот»: len(«кот») = 3.
  4. Ключ слова «дом»: len(«дом») = 3.
  5. Ключ слова «окно»: len(«окно») = 4.
  6. Слова встали в порядке ключей. Слова с равным ключом — «кот», «дом» — сохранили исходный порядок.

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

key=len
По длине: 3, 3, 4, 5.
без key
Без ключа слова сравниваются сами — по алфавиту, буква за буквой.
reverse=True
Тот же ключ, обратный порядок: от длинных к коротким. Равные «кот» и «дом» по-прежнему в исходном порядке.
последняя буква
Ключ lambda w: w[-1] — последняя буква: «з» раньше «м», «о» и «т».

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

Несколько критериев задают кортежем: кортежи сравниваются слева направо. Кортежи сравниваются лексикографически: сначала первые элементы; если они равны — вторые, и так далее. Это похоже на порядок слов в словаре, но применяется к любым сравнимым элементам.

students = [("Анна", 91), ("Илья", 84), ("Марк", 91)]ordered = sorted(    students, key=lambda student: (-student[1], student[0]))print(ordered)
[('Анна', 91), ('Марк', 91), ('Илья', 84)]

Балл сортируется по убыванию благодаря минусу: 91 превращается в −91, что меньше −84. При равных баллах имя сортируется по возрастанию. reverse=True для всего кортежа развернул бы оба критерия, поэтому это не то же самое. Как кортежи ключей решают порядок — по шагам:

  1. Исходные записи: имя и балл.
  2. Ключ каждой записи — кортеж: минус балл и имя.
  3. Сначала сравниваются первые элементы: −91 меньше остальных, поэтому Анна и Марк будут впереди.
  4. У Анна и Марк первые элементы равны — решает второй: имена сравниваются по алфавиту.
  5. Итог: по убыванию балла, при равном балле — по алфавиту.

Устойчивость и max

Сортировка устойчива, а один лучший элемент находят без сортировки. Сортировка Python устойчива: элементы с одинаковым ключом сохраняют взаимный исходный порядок. Например, слова "кот" и "дом" имеют одинаковую длину и остались в исходной последовательности.

Если нужен только лучший элемент, нет необходимости сортировать всё:

words = ["арбуз", "кот", "дом", "окно"]longest = max(words, key=len)print(longest)print(min([], default=None))
арбуз None

max проходит элементы один раз и помнит лучший; при одинаковом ключе лучшим остаётся первый. Для возможно пустого источника можно указать default=None. Как max выбирает — по шагам:

  1. «кот»: длина 3 — первый элемент, пока лучший.
  2. «слон»: 4 > 3 — новый лучший.
  3. «мышь»: 4 не больше 4 — лучшим остаётся «слон».
  4. «ёж»: 2 < 4 — лучшим остаётся «слон».
  5. Список кончился: 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)
3 70
  1. Цены по возрастанию: выгоднее всего брать самые дешёвые.
  2. Пробуем 10: 0 + 10 = 10 — помещается.
  3. Купили 10: потрачено 10.
  4. Пробуем 20: 10 + 20 = 30 — помещается.
  5. Купили 20: потрачено 30.
  6. Пробуем 40: 30 + 40 = 70 — помещается.
  7. Купили 40: потрачено 70.
  8. 70 + 50 = 120 > 100: не помещается — break.
  9. Итог: 3 предмета за 70.

Самые дешёвые 10, 20 и 40 стоят 70; следующая цена 50 уже не помещается. Никакие четыре предмета не уложатся в 100: четыре самых дешёвых стоят 120.

Но сортировка по одному показателю не решает произвольный выбор оптимального набора. Если у предметов разные цены и полезности, выбор по цене или отношению полезности к цене для неделимых предметов может оказаться неверным. Сначала доказательство подхода, затем код.

Итог

Любой порядок задаётся ключом, а несколько критериев — кортежем ключей. Вот что понадобится дальше — и в задачах ЕГЭ с сортировкой.

  • sorted и sortsorted(x) создаёт новый список, x.sort() меняет x и возвращает None; reverse=True — по убыванию.
  • key=функцияkey=функция сортирует по значению функции; передаётся функция без скобок.
  • Несколько критериевНесколько критериев — кортеж ключей; минус перед числом даёт убывание только этого критерия.
  • Лучший без сортировкиСортировка устойчива; один лучший элемент — max(x, key=…), пустой источник — default=.
  • Жадный выборЖадный выбор после сортировки верен только с доказательством.

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1

Что дадут sorted(["10", "9", "100"]) и sorted(["10", "9", "100"], key=int)?

ОтветСкрыть ответ
Ответ

['10', '100', '9'] и ['9', '10', '100'].

Вопрос 2

Чему равно b после a = [3, 1, 2] и b = a.sort()? Чему равно a?

ОтветСкрыть ответ
Ответ

b = None, a = [1, 2, 3].

Вопрос 3

Отсортируй [("Ира", 80), ("Аня", 92), ("Боря", 80)] по убыванию балла, а при равенстве — по имени.

ОтветСкрыть ответ
Ответ

[('Аня', 92), ('Боря', 80), ('Ира', 80)].

Вопрос 4

Что вернёт max(["кот", "слон", "мышь"], key=len) и почему?

ОтветСкрыть ответ
Ответ

слон: у «слон» и «мышь» длина 4, max оставляет первый.

Вопрос 5

Чем 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.

Напиши программу: сколько предметов на бюджет

Цены [30, 5, 25, 15, 60], бюджет 50. Напиши программу, которая покупает самые дешёвые предметы, пока хватает денег, и печатает количество и потраченную сумму. Запусти её и запиши, что она напечатала.

Запишите два числа через пробел: количество, затем потраченную сумму.

Напиши программу: сортировка по сумме цифр

Напиши программу, которая сортирует [15, 7, 22, 9, 30, 4] по сумме цифр, а при равенстве — по самому числу, и печатает результат. Запусти её и запиши, что она напечатала.

Запишите список так, как его печатает Python.