Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 13 из 20 Начальный 20 мин 120 XP

Сортировка словаря по ключам и значениям

Словарь нельзя отсортировать на месте — но можно получить отсортированный: sorted по ключам, key=lambda по значениям, вторичный ключ кортежем, top-N срезом и dict(sorted(...)) обратно.

Редакция Питоники

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

sorted: ключи и пары

по алфавиту
prices = {'сыр': 590, 'молоко': 89, 'хлеб': 42}

print(sorted(prices))
print(sorted(prices.items()))
Вывод
['молоко', 'сыр', 'хлеб']
[('молоко', 89), ('сыр', 590), ('хлеб', 42)]

Три обзора словаря: keys, values и items

Прежде чем сортировать, стоит решить, что именно вы сортируете. У словаря три обзора, и каждый даёт свой список. sorted(prices) возвращает ключи — названия товаров, по алфавиту. sorted(prices.values()) — только цены, и названия рядом с ними уже нет. sorted(prices.items()) — пары целиком: ключ остаётся рядом со своим значением.

три обзора одного словаря
prices = {'сыр': 590, 'молоко': 89, 'хлеб': 42}

print(sorted(prices))
print(sorted(prices.values()))
print(sorted(prices.items()))
Вывод
['молоко', 'сыр', 'хлеб']
[42, 89, 590]
[('молоко', 89), ('сыр', 590), ('хлеб', 42)]

Третий обзор — рабочий: именно из списка пар собирают новый словарь, по нему проходят циклом, его режут срезом, чтобы взять топ. Первые два пригодятся там, где имена не нужны: посчитать сумму, найти максимум, сравнить две выборки. И мелочь, на которой спотыкаются: sorted(prices) и sorted(prices.keys()) дают одинаковый результат — словарь и так перебирается по ключам.

По значениям: key=lambda

от дешёвого к дорогому и обратно
prices = {'сыр': 590, 'молоко': 89, 'хлеб': 42}

by_price = sorted(prices.items(), key=lambda pair: pair[1])
print(by_price)

top = sorted(prices.items(), key=lambda pair: pair[1], reverse=True)
print(top)
Вывод
[('хлеб', 42), ('молоко', 89), ('сыр', 590)]
[('сыр', 590), ('молоко', 89), ('хлеб', 42)]

Без аргумента sorted идёт по ключам — и у словаря, и у пар: кортежи сравниваются по первому элементу, то есть тоже по ключу. Чтобы сортировать по цене, аргументу key передают функцию, которую sorted применяет к каждой паре: lambda pair: pair[1] — мини-функция, берущая второй элемент кортежа. По этим ценам пары и выстраиваются. Флаг reverse=True переворачивает порядок: получился рейтинг от дорогого к дешёвому.

dict(sorted(...)): новый словарь

словарь в заданном порядке
prices = {'сыр': 590, 'молоко': 89, 'хлеб': 42}

cheap_first = dict(sorted(prices.items(), key=lambda pair: pair[1]))
print(cheap_first)
print(prices)
Вывод
{'хлеб': 42, 'молоко': 89, 'сыр': 590}
{'сыр': 590, 'молоко': 89, 'хлеб': 42}

dict() собирает словарь из списка пар, а пары в нём хранятся в порядке вставки — том самом, который задал sorted. Поэтому дешёвые товары и остались первыми. sorted не трогает исходный словарь: она возвращает отсортированный список пар, из которого собирается новый словарь. Хотите и то и другое — держите два словаря: исходный и отсортированный, а склеить их с другими данными поможет урок 14 про объединение словарей.

ЗаписьЧто получитсяКогда брать
sorted(d)Список ключей по возрастаниюГлянуть имена по алфавиту
sorted(d.items(), key=lambda pair: pair[1])Список пар по значениямТопы и рейтинги; reverse=True для убывания
dict(sorted(d.items(), ...))Новый словарь в заданном порядкеХранить и печатать в порядке

Как отсортировать словарь по двум признакам сразу

Одна сортировка по цене оставляет вопрос: а как упорядочить товары с одинаковой ценой? Ответ — sorted со стабильной сортировкой. Порядок равных элементов сохраняется, но «равные» тут означают «равные по цене», а не равные целиком. Чтобы решение было воспроизводимым, добавьте второй признак: ключ-кортеж, где первым идёт цена, вторым — название.

цена, потом название
goods = [('сыр', 590), ('хлеб', 42), ('масло', 590), ('молоко', 89)]

print(sorted(goods, key=lambda pair: (-pair[1], pair[0])))
Вывод
[('масло', 590), ('сыр', 590), ('молоко', 89), ('хлеб', 42)]

Разбор ключа по частям. Минус перед pair[1] разворачивает цену: без него пары встали бы по возрастанию, а нам нужен рейтинг от дорогих к дешёвым. Второй элемент кортежа — pair[0], название — в порядке возрастания. Кортежи Python сравнивает поэлементно, и как только первые элементы совпали, сравнение переходит ко вторым. Именно поэтому масло стоит раньше сыра: цены одинаковые, а по алфавиту «м» раньше «с».

Обратите внимание на обратный вариант — с reverse=True в конце. Флаг переворачивает весь порядок целиком, включая вторичный признак: цены идут по возрастанию, а при равных ценах названия — по убыванию. Для рейтинга это почти никогда не нужно, но знать о таком поведении полезно: тот же ключ с флагом даёт противоположный результат, и ошибка выглядит как «сортировка сломалась», хотя на самом деле просто развернулась.

тот же ключ с флагом
goods = [('сыр', 590), ('хлеб', 42), ('масло', 590), ('молоко', 89)]

print(sorted(goods, key=lambda pair: (-pair[1], pair[0]), reverse=True))
Вывод
[('хлеб', 42), ('молоко', 89), ('сыр', 590), ('масло', 590)]

Как получить топ-3 самых дорогих товара

Отсортированный список — обычный список, а значит, к нему применимы все приёмы работы со списками, включая срез. Витрина магазина с тремя самыми дорогими позициями получается одной строкой: sorted(...)[:3]. Порядок важен — сначала сортировка, потом срез, иначе вы срежете первые три пары в порядке вставки, а не самые дорогие.

три самых дорогих
stock = {'хлеб': 42, 'молоко': 89, 'сыр': 590, 'чай': 310, 'масло': 250}

top = sorted(stock.items(), key=lambda pair: pair[1], reverse=True)[:3]

print(top)
Вывод
[('сыр', 590), ('чай', 310), ('масло', 250)]

Срез работает и по словарю напрямую — list(stock.items())[:3] даст три первые пары в порядке вставки, но это не «топ», это просто начало. Разница между этими двумя строками стоит целого отчёта: первый вариант отсортирован, второй нет. Если вам нужен топ, но не нужен весь отсортированный список, режьте после сортировки — Python всё равно построит весь список, но вы покажете только нужное.

Почему при сортировке по ключу важен регистр

Отсортированный список ключей выглядит неловко, когда в словаре смешаны заглавные и строчные буквы. Причина в том, что строки Python сравнивает по кодовым точкам, а не по алфавиту: у заглавных букв коды меньше, чем у строчных, поэтому «Яблоко» встаёт раньше «яблока», а «Абрикос» — раньше «банана». Порядок получается не «по алфавиту», а «сначала всё заглавное, потом всё строчное».

алфавит против кодов
names = {'Яблоко': 3, 'яблоко': 5, 'Абрикос': 2, 'банан': 4}

print(sorted(names))
print(sorted(names, key=lambda name: name.lower()))
Вывод
['Абрикос', 'Яблоко', 'банан', 'яблоко']
['Абрикос', 'банан', 'Яблоко', 'яблоко']

Второй вывод — тот, который обычно и нужен: с ключом key=lambda name: name.lower() сортировка идёт по приведённому к нижнему регистру имени, и «Абрикос» встаёт рядом с «бананом». Настоящий алфавит и коды точек — разные вещи, и подвох ждёт именно там, где словарь наполняют данные из разных источников: при импорте из базы имена приходят с заглавной буквы, из формы — со строчной. Отдельно про «ё»: у строчной «ё» код 1105, она больше даже «я» (1103) и потому уезжает в конец списка, а заглавная «Ё» с кодом 1025, наоборот, оказывается в самом начале.

Что делать, если ключи разного типа

Словарь с ключами разных типов — не редкость, если данные приходят из JSON: там ключи всегда строки, а внутри программы кто-то пользуется числами. Само создание такого словаря проходит без ошибок, а вот сортировка по ключам падает: Python не умеет сравнивать строку с числом и честно об этом сообщает.

строки и числа в одном словаре
mixed = {'a': 1, 2: 'два'}

print(sorted(mixed))
Вывод
Traceback (most recent call last):
  File "example.py", line 3, in <module>
    print(sorted(mixed))
          ~~~~~~~~~~~~^
TypeError: '<' not supported between instances of 'int' and 'str'
Блок не запускается: код специально падает. Указатель в traceback показывает, на каком именно месте стоит sorted — на месте вызова.

Читать ошибку надо по существу: Python не может решить, что больше — «a» или 2, потому что это значения разных вселенных. Лечится приведением ключей к одному типу на входе: {str(k): v for k, v in raw.items()} — и словарь сразу становится предсказуемым. Привычка приводить ключи к строке спасает и в обратную сторону: из JSON ключи приходят строками всегда, и код, который ищет data[1], не найдёт ничего, потому что нужен data['1'].

Сценарий: рейтинг товаров для витрины

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

витрина, отчёт, максимум
catalog = {'хлеб': 42, 'молоко': 89, 'сыр': 590, 'чай': 310}

showcase = sorted(catalog.items(), key=lambda pair: pair[1], reverse=True)[:3]

for name, price in showcase:
    print(f'{name} — {price} руб.')

print('---')
print(sorted(catalog))
print('---')
print(max(catalog, key=catalog.get))
Вывод
сыр — 590 руб.
чай — 310 руб.
молоко — 89 руб.
---
['молоко', 'сыр', 'хлеб', 'чай']
---
сыр

Что здесь важно: catalog остался нетронутым. Все три ответа получены из одного словаря тремя разными способами, и ни один из них не переписал исходные данные. Если бы мы решили «навести порядок» через catalog = dict(sorted(...)), то отчёт по алфавиту и витрина по цене рано или поздно разошлись бы: один и тот же объект, переиспользованный под две задачи, требует постоянной синхронизации. Мы в одном проекте так и сделали — и через две недели обнаружили, что «отчёт по алфавиту» внезапно перестал быть алфавитным. С тех пор держим правило: словарь с данными один, а порядок каждый раз получают заново.

ЗадачаЗаписьЧто получится
Отсортировать по алфавитуsorted(d)Список ключей, при равных — по порядку вставки
Отсортировать по ценеsorted(d.items(), key=lambda p: p[1])Список пар, цена по возрастанию
Отсортировать по убыванию ценыто же + reverse=TrueСписок пар, цена по убыванию
Топ-3 дорогихsorted(...)[:3]Три пары с конца отсортированного списка
Сортировать без учёта регистраkey=lambda p: p[0].lower()Список пар по приведённому имени
Найти самую дорогую позициюmax(d, key=d.get)Один ключ — имя лидера
Собрать словарь в нужном порядкеdict(sorted(d.items(), ...))Новый dict с порядком вставки

Что дальше

Сортировка разобрана — теперь словари умеют ещё и объединяться. В уроке 14 разбираем update и оператор |, включая главный вопрос слияния: чья пара выживет, когда ключ встретился в обоих словарях. А если вместо сортировки вам нужен подсчёт — как в частотном словаре текста — загляните в урок про Counter, там готовый счётчик считает частоты одной строкой.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

d = {'банан': 2, 'яблоко': 1, 'вишня': 3}

print(sorted(d))
prices = {'чай': 50, 'кофе': 120}

print(sorted(prices.items(), key=lambda pair: pair[1])[-1][0])
prices = {'сыр': 590, 'хлеб': 42}

cheap = dict(sorted(prices.items(), key=lambda pair: pair[1]))
print(cheap)
Проверь себя
0 / 8

1. Что вернёт sorted(prices) для словаря?

2. По чему sorted(prices.items()) сортирует пары по умолчанию?

3. Что делает key=lambda pair: pair[1]?

4. Меняет ли sorted исходный словарь?

5. Почему dict(sorted(...)) сохраняет заданный порядок?

6. Что вернёт sorted(prices.values())?

7. Зачем в ключе сортировки пишут кортеж: (-pair[1], pair[0])?

8. Что произойдёт при sorted({'a': 1, 2: 'два'})?

Карточки терминов
Запомнено: 0 / 10
Практика

Дан словарь prices = {'сыр': 590, 'молоко': 89, 'хлеб': 42}. Выведи список пар от дешёвого к дорогому, затем собери из него словарь и выведи его.

practice.py
Вопросы и ответы по уроку

Как отсортировать словарь по значениям в Python?

sorted(prices.items(), key=lambda pair: pair[1]) вернёт список пар от меньшего значения к большему. Для убывания добавьте reverse=True, а чтобы получить словарь в новом порядке — оберните результат в dict().

Урок 8: перебор через items

Как отсортировать словарь по убыванию?

Тем же sorted с флагом reverse=True: sorted(prices.items(), key=lambda pair: pair[1], reverse=True). Подойдёт и отрицание в ключе — key=lambda pair: -pair[1], если значения числовые.

Почему у словаря нет метода sort?

Метод sort сортирует список на месте, а словарь — это пары «ключ — значение», упорядоченные вставкой. Сортировка словаря всегда создаёт новый список пар, который при желании превращается обратно в словарь функцией dict().

Сохранится ли порядок после dict(sorted(...))?

Да: словарь хранит пары в порядке вставки, а вставляет он их из отсортированного списка. Порядок, заданный sorted, станет порядком нового словаря и сохранится при печати и переборе.

Как отсортировать словарь по алфавиту без учёта регистра?

Добавьте в key приведение к нижнему регистру: sorted(d.items(), key=lambda pair: pair[0].lower()). Без этого заглавные буквы встанут раньше строчных, потому что строки сравниваются по кодовым точкам, а не по алфавиту.

Как получить три самых дорогих товара из словаря?

Отсортируйте пары по значению по убыванию и возьмите срез: sorted(d.items(), key=lambda pair: pair[1], reverse=True)[:3]. Сначала сортировка, потом срез — в обратном порядке вы получите просто первые три пары словаря.

Можно ли отсортировать словарь на месте, как список?

Нет. У словаря нет метода sort, и prices.sort() падает с AttributeError: 'dict' object has no attribute 'sort'. Сортировка всегда даёт новый список; чтобы получить словарь в нужном порядке, соберите его заново: dict(sorted(d.items(), ...)).

Почему sorted(d) и sorted(d.keys()) дают одинаковый результат?

Потому что словарь перебирается по ключам, и sorted получает ровно тот же набор, что и sorted(d.keys()). Значения так не отсортировать — для них есть sorted(d.values()), а пары целиком — sorted(d.items()).

Понравился урок? Сошлитесь на него

«sorted не трогает исходный словарь: она возвращает отсортированный список пар, из которого собирается новый словарь.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.