+7 (495) 147-04-32
Главная
/
Блог
/
Разработчикам
/
Как поменять ключи и значения в словаре Python

Задача для собеседования: как поменять ключи и значения в словаре Python

Как поменять ключи и значения в словаре Python
Есть еще одна задачка, которая отлично проверяет знание словарей в Python.

Задача


Есть простой словарь, например: my_dict = {'a': 1, 'b': 2, 'c': 3}


Нужно реализовать функцию, которая поменяет местами ключ и значение исходного словаря.

Казалось бы, довольно простая задачка — берем исходный словарь, разбиваем ключ — значение и записываем в новый словарь.

Но в этом есть свой подвох: нужно будет предусмотреть несколько кейсов и обговорить их на собеседовании перед выполнением этой задачи.

Что мы знаем о словарях в Python?

Это такая структура данных для хранения информации. Они представляют собой неупорядоченные (до Python 3.7) или упорядоченные (начиная с Python 3.7+) коллекции элементов, где каждый элемент хранится в виде пары «ключ-значение».

1. Пары «ключ-значение»это фундаментальная структура словаря. Каждый элемент состоит из уникального ключа и связанного с ним значения.
 

2. Ключи должны быть уникальны и хешируемы. В одном словаре не может быть двух одинаковых ключей. Если вы попытаетесь добавить пару с уже существующим ключом, значение для этого ключа будет перезаписано. Ключи должны быть объектами неизменяемых (immutable) типов, для которых можно вычислить хэш-значение. Это необходимо для быстрой внутренней реализации поиска.
 

3. Значения могут быть любыми. Значения в словаре могут быть объектами любого типа данных: числа, строки, списки, другие словари, функции, объекты классов и т.д. Один словарь может содержать значения разных типов.
 

4. Изменяемость (Mutable): словари являются изменяемыми.
 

5. Порядок элементов: до Python 3.6 (включая официальную спецификацию) словари считались неупорядоченными. Это означало, что порядок, в котором добавляли элементы, не обязательно сохранялся при итерации по словарю или его печати. В реализации CPython 3.6 (наиболее распространенной) словари стали упорядоченными по факту сохранения порядка вставки, но это считалось деталью реализации, на которую не следовало полагаться.

Начиная с версии Python 3.7, сохранение порядка вставки элементов стало официальной частью спецификации языка. То есть, словари помнят порядок, в котором были добавлены элементы. popitem() также стал удалять последний добавленный элемент (LIFO).
 

6. Быстрый доступ по ключу: благодаря использованию хэш-таблиц для внутреннего хранения, доступ к значению по ключу, добавление новой пары и удаление пары выполняются очень быстро, в среднем за O(1) (константное время), независимо от размера словаря. В худшем случае (из-за коллизий хэшей) это может быть O(n).

Что нужно будет предусмотреть (ключевые моменты для обсуждения на собеседовании)

Хешируемость значений

  • Ключи в словаре Python должны быть хешируемыми.
     
  • Исходные значения словаря станут ключами в новом словаре. Если среди значений есть нехешируемые типы (например, списки, другие словари, кастомные объекты без __hash__), то при попытке использовать их как ключ возникнет TypeError.
     

Вариант решения: проверять тип значения или пытаться его хешировать (hash(value)) внутри try-except блока. Если нехешируемо, то либо пропустить, либо вызвать ошибку (предпочтительно), либо попытаться преобразовать (например, список в кортеж, если это осмысленно).
 

Дублирующиеся значения в исходном словаре

  • Ключи в словаре должны быть уникальными. Если в исходном словаре несколько разных ключей имеют одинаковое значение (например, {'a': 1, 'b': 1}), то при инвертировании это значение (1) попытается стать ключом дважды.
     
  • Последствия (без специальной обработки): «Последний выиграет». В примере {'a': 1, 'b': 1} инвертированный словарь будет 
    {1: 'b'} (если b обрабатывается позже a), и информация о паре 
    'a': 1 будет потеряна.

Варианты решения:

1. Вызвать ошибку ValueError. Это самый безопасный способ, так как он явно указывает на потенциальную потерю данных.
 

2. Перезаписывать (поведение по умолчанию для dict comprehension). Последнее встреченное значение для данного ключа перезапишет предыдущее. Это приводит к потере данных.
 

3. Собирать исходные ключи в список/кортеж. Если значение-дубликат встречается, то в новом словаре значением для этого ключа становится список (или кортеж) всех исходных ключей, которые имели это значение. Например, {'a': 1, 'b': 1} превратится в {1: ['a', 'b']}. Это изменяет структуру результирующего словаря (значениями становятся списки), но сохраняет всю информацию.
 

4. Пропускать дубликаты.


Пустой словарь

Функция должна корректно обрабатывать пустой словарь, возвращая пустой словарь.


Тип возвращаемого значения

Обычно ожидается, что функция вернет новый словарь, не изменяя исходный.

На собеседовании важно не просто написать код, а именно обсудить эти моменты, показав понимание структуры данных «словарь» и потенциальных проблем.

Вариант с dict comprehension ({value: key for key, value in d.items()}) очень лаконичен, но стоит упомянуть его ограничения (молчаливая перезапись при дубликатах, менее явная обработка ошибок хеширования).

Теперь перейдем к реализации:
 

def invert_dictionary(*, original_dict: dict) -> dict:
    """
    Меняет местами ключи и значения в словаре

    Args:
        original_dict (dict): Исходный словарь.

    Returns:
        dict: Новый словарь, где значения стали ключами, а ключи - значениями.

    Raises:
        TypeError: Если какое-либо значение в исходном словаре не является хешируемым
                   (например, список), так как оно не может быть ключом.
        ValueError: Если в исходном словаре есть дублирующиеся значения, что привело бы
                    к потере данных при инвертировании (поскольку ключи должны быть уникальны).
                    Либо можно выбрать другую стратегию обработки дубликатов.
    """

    inverted_dict = {}
    for key, value in original_dict.items():
        # 1. Проверка на хешируемость значения (будущего ключа)
        try:
            hash(value)
        except TypeError:
            raise TypeError(f"Значение '{value}' (из ключа '{key}') не является хешируемым и не может стать ключом.")

        # 2. Проверка на дубликаты значений (будущих ключей)
        if value in inverted_dict:
            # Здесь можно выбрать разную стратегию:
            # А) Вызвать ошибку (самый безопасный вариант, чтобы не потерять данные)
			# Б) Перезаписать (последнее вхождение "победит" - как в dict comprehension)
			# В) Собрать исходные ключи в список (если это допустимо по условиям)
            raise ValueError(
                f"Обнаружено дублирующееся значение '{value}'. "
                f"Ключи '{inverted_dict[value]}' и '{key}' оба указывают на это значение. "
                f"Невозможно создать уникальные ключи в инвертированном словаре без потери данных."
            )
        else:
            inverted_dict[value] = key
            # Если выбрана стратегия В (сбор в список), то первое вхождение тоже должно быть списком
            # inverted_dict[value] = [key]
            
    return inverted_dict

# Пример использования
my_dict = {'a': 1, 'b': 2, 'c': 3}
try:
    inverted = invert_dictionary(my_dict)
    print(f"Исходный словарь: {my_dict}")
    print(f"Инвертированный словарь: {inverted}")
except (TypeError, ValueError) as e:
    print(f"Ошибка: {e}")

print("-" * 20)

# Пример с нехешируемым значением
dict_with_unhashable = {'a': [1, 2], 'b': 3}
try:
    inverted_unhashable = invert_dictionary(dict_with_unhashable)
    print(f"Инвертированный (неудачно): {inverted_unhashable}")
except (TypeError, ValueError) as e:
    print(f"Исходный словарь: {dict_with_unhashable}")
    print(f"Ошибка при инвертировании: {e}")

print("-" * 20)

# Пример с дублирующимися значениями
dict_with_duplicates = {'a': 1, 'b': 2, 'c': 1}
try:
    inverted_duplicates = invert_dictionary(dict_with_duplicates)
    print(f"Инвертированный (неудачно): {inverted_duplicates}")
except (TypeError, ValueError) as e:
    print(f"Исходный словарь: {dict_with_duplicates}")
    print(f"Ошибка при инвертировании: {e}")

print("-" * 20)

# Более короткий вариант с использованием dict comprehension (НО! он молча перезаписывает при дубликатах)
def invert_dictionary_comprehension(original_dict):
    try:
        return {value: key for key, value in original_dict.items()}
    except TypeError as e:
        raise TypeError(f"Одно из значений не хешируемо и не может быть ключом. Ошибка Python: {e}")

my_dict_comp = {'x': 10, 'y': 20, 'z': 30}
inverted_comp = invert_dictionary_comprehension(my_dict_comp)
print(f"Исходный (comprehension): {my_dict_comp}")
print(f"Инвертированный (comprehension): {inverted_comp}")

dict_duplicates_comp = {'a': 1, 'b': 2, 'c': 1} # 'a' будет перезаписано 'c' для ключа 1
inverted_duplicates_comp = invert_dictionary_comprehension(dict_duplicates_comp)
print(f"Исходный с дубликатами (comprehension): {dict_duplicates_comp}")
print(f"Инвертированный с дубликатами (comprehension): {inverted_duplicates_comp}") # {1: 'c', 2: 'b'}
В варианте реализации invert_dictionary_comprehension молча перезапишет значения, если исходные значения дублируются, не проверяет на хешируемость до момента попытки создания ключа. Для собеседования лучше сначала показать более развернутый вариант с проверками.

Вопросы

Можно ли использовать dict comprehension для инвертирования словаря?
Что делать, если значения в исходном словаре не хешируемы?
Изменяется ли исходный словарь при инвертировании?

Поделиться

Обсудить проект с командой LighTech

Забронировать встречу

Примеры реализации проектов

Обсудить проект
Имя
Связаться
Сообщение
Прикрепить файл +
Запрос на получение файлов
Имя
Отправить файлы
Сообщение
Спасибо!
Ваша заявка отправлена
После обработки наш менеджер свяжется с вами