Сортировка словаря по ключам и значениям
Словарь нельзя отсортировать на месте — но можно получить отсортированный: 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'Читать ошибку надо по существу: 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)
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: 'два'})?
Дан словарь prices = {'сыр': 590, 'молоко': 89, 'хлеб': 42}. Выведи список пар от дешёвого к дорогому, затем собери из него словарь и выведи его.
Как отсортировать словарь по значениям в 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-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Словари · Урок 8
Перебор словаря: keys, values и items
Три представления словаря — keys, values, items: перебираем цены в цикле for, распаковываем пары в две переменные, считаем сумму и фильтруем дешёвые позиции.
Словари · Урок 12
defaultdict: словарь с заводским умолчанием
defaultdict сам создаёт значение при первом обращении: списки для группировки и нули для счётчиков — без setdefault и проверок. Плюс factory, рецепты по типам и грабли с лишними ключами.
Словари · Урок 14
Объединение словарей: update и оператор |
Два словаря в один: update переписывает на месте, оператор | собирает новый словарь, слои настроек ставятся по приоритету, а при конфликте ключей побеждает правый.
Функции · Урок 9
sorted с key в Python: сортировка по правилу
Порядок по своему правилу: слова по длине, пары по цене, участники по очкам. Разбираем sorted, параметр key, reverse и стабильность Timsort.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
json · Урок 16
Сортировка и топ-N: sorted по ключу
Список из API приходит в том порядке, в каком отдал сервер: sorted с key наводит порядок, reverse разворачивает, срез берёт топ-3.
python сортировка списка словарейsorted key lambda python
Datetime · Урок 7
Сравнение и сортировка дат
Даты сравниваются как числа — и потому сортируются, выбираются min и max и становятся ключами словаря. Разбираем операторы и честную сортировку без строковых сюрпризов.
sorted дат pythonсортировка дат python
Словари · Урок 10
setdefault: значение при первом обращении
setdefault возвращает значение по ключу, а если ключа нет — сначала записывает умолчание: группировка гостей по городам без if-проверок, счётчики и подводный камень с вычислением аргумента.
setdefault словарь pythonгруппировка словарь python