← все задачи
Python · задача 3 из 10
Разложить вложенный словарь в плоский
Средний
15 минут
рекурсиягенераторыкраевые случаи
Условие
Напишите функцию, которая превращает вложенный словарь в плоский: ключи склеиваются через точку. {"db": {"host": "localhost", "port": 5432}} превращается в {"db.host": "localhost", "db.port": 5432}.
Что требуется
- Глубина вложенности произвольная
- Разделитель задаётся параметром, по умолчанию точка
- Пустой вложенный словарь не должен потерять ключ целиком — обсудите, что с ним делать
Пример
data = {
"db": {"host": "localhost", "port": 5432},
"debug": True,
"cache": {"redis": {"ttl": 60}},
}
flatten(data)
# {"db.host": "localhost", "db.port": 5432,
# "debug": True, "cache.redis.ttl": 60}
Сначала уточните
Вопросы до кода — половина оценки. Молча начать печатать хуже, чем задать два вопроса.
- Что делать со списками: разворачивать по индексам (items.0.name) или оставлять как значение?
- Что с пустым вложенным словарём — выбросить ключ или оставить {} как значение?
- Может ли точка встречаться в самих ключах — тогда результат неоднозначен
Показать решение Скрыть решение
Решение
def flatten(data, separator=".", prefix=""):
result = {}
for key, value in data.items():
full_key = f"{prefix}{separator}{key}" if prefix else str(key)
if isinstance(value, dict) and value:
result.update(flatten(value, separator, full_key))
else:
# пустой словарь — это тоже значение, иначе ключ пропадёт
result[full_key] = value
return result
# Вариант на генераторе: не строит промежуточные словари на каждом уровне
def flatten_items(data, separator=".", prefix=""):
for key, value in data.items():
full_key = f"{prefix}{separator}{key}" if prefix else str(key)
if isinstance(value, dict) and value:
yield from flatten_items(value, separator, full_key)
else:
yield full_key, value
Почему так
Почему рекурсия здесь уместна
- Структура данных сама рекурсивная: словарь внутри словаря — решение повторяет форму задачи
- Глубина вложенности конфигов — единицы уровней, до предела рекурсии (1000) не дойдёт
- Если данные приходят извне и глубина не ограничена, скажите об этом и предложите явный стек — это плюс к ответу
Почему isinstance(value, dict) and value
- Без проверки на пустоту ключ с {} исчез бы из результата — данные потерялись бы молча
- Молчаливая потеря данных на интеграции — худший вид бага: заметят через месяц и не там
- Явное решение по краевому случаю ценнее «правильного» ответа: покажите, что он вообще есть
Зачем вариант с генератором
- Рекурсивный update копирует накопленное на каждом уровне, генератор отдаёт пары по одной
- Из генератора легко собрать dict(flatten_items(data)) или сразу писать в файл, не держа всё в памяти
- yield from — короткая форма проброса, и вопрос «чем отличается от yield в цикле» звучит следом очень часто
Что спросят дальше
- Обратная задача: unflatten — соберите словарь назад, это частое продолжение
- Спросят про списки: items.0.name — покажите ветку с enumerate, но сначала уточните, нужна ли она
- Коллизия ключей: {"a.b": 1, "a": {"b": 2}} даст один ключ — назовите это ограничением решения
Следующая задача
Дубликаты и нарезка на пачки — Две микрозадачи, которые почти всегда идут вместе: убрать дубли с сохранением порядка и отдать данные батчами.