← Назад к вопросам

Объединение словарей с суммированием

1.6 Junior🔥 171 комментариев
#Python Core

Условие

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

Пример

merge_dicts({"a": 1, "b": 2}, {"b": 3, "c": 4}) → {"a": 1, "b": 5, "c": 4}

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Объединение словарей с суммированием

Эта классическая задача требует объединения двух словарей с агрегацией значений по ключам. Это полезный паттерн при работе с числовыми данными, счётчиками, статистикой и многим другим.

Простое решение

def merge_dicts(dict1: dict, dict2: dict) -> dict:
    """
    Объединяет два словаря, суммируя значения по совпадающим ключам.
    
    Args:
        dict1: Первый словарь
        dict2: Второй словарь
    
    Returns:
        Новый словарь с объединёнными данными
    
    Examples:
        >>> merge_dicts({"a": 1, "b": 2}, {"b": 3, "c": 4})
        {'a': 1, 'b': 5, 'c': 4}
        >>> merge_dicts({"x": 10}, {"y": 20})
        {'x': 10, 'y': 20}
    """
    result = dict1.copy()  # Копируем первый словарь
    
    # Проходим по второму словарю и суммируем
    for key, value in dict2.items():
        if key in result:
            result[key] += value
        else:
            result[key] = value
    
    return result

Решение с использованием get()

def merge_dicts_get(dict1: dict, dict2: dict) -> dict:
    """
    Альтернативное решение с использованием метода get().
    """
    result = dict1.copy()
    
    for key, value in dict2.items():
        # Если ключа нет, get() вернёт 0 (для чисел)
        result[key] = result.get(key, 0) + value
    
    return result

Решение с использованием setdefault()

def merge_dicts_setdefault(dict1: dict, dict2: dict) -> dict:
    """
    Решение с использованием setdefault().
    """
    result = dict1.copy()
    
    for key, value in dict2.items():
        # setdefault() возвращает существующее значение или устанавливает новое
        result.setdefault(key, 0)
        result[key] += value
    
    return result

Решение со слиянием в place (изменение исходного словаря)

def merge_dicts_inplace(dict1: dict, dict2: dict) -> dict:
    """
    Объединение со слиянием в исходный словарь.
    Примечание: изменяет dict1!
    """
    for key, value in dict2.items():
        dict1[key] = dict1.get(key, 0) + value
    
    return dict1

Решение с использованием Counter

from collections import Counter

def merge_dicts_counter(dict1: dict, dict2: dict) -> dict:
    """
    Решение с использованием Counter из collections.
    Очень элегантно и эффективно для целых чисел.
    """
    counter1 = Counter(dict1)
    counter2 = Counter(dict2)
    
    # Counter поддерживает сложение
    result = counter1 + counter2
    
    return dict(result)

Решение с использованием функциональных подходов

from itertools import chain
from functools import reduce
from typing import Dict, Callable

def merge_dicts_reduce(dict1: dict, dict2: dict) -> dict:
    """
    Функциональный подход с reduce().
    """
    def merge_two(acc, item):
        key, value = item
        acc[key] = acc.get(key, 0) + value
        return acc
    
    # Объединяем все элементы и применяем merge_two
    return reduce(merge_two, chain(dict1.items(), dict2.items()), {})

Универсальное решение для любых типов данных

from typing import TypeVar, Callable, Any

K = TypeVar('K')  # Тип ключа
V = TypeVar('V')  # Тип значения

def merge_dicts_custom(
    dict1: dict,
    dict2: dict,
    merge_func: Callable[[Any, Any], Any] = lambda x, y: x + y
) -> dict:
    """
    Универсальное решение с пользовательской функцией слияния.
    
    Args:
        dict1: Первый словарь
        dict2: Второй словарь
        merge_func: Функция для слияния значений (по умолчанию сумма)
    
    Returns:
        Объединённый словарь
    
    Examples:
        >>> merge_dicts_custom({"a": 1}, {"a": 2}, lambda x, y: x + y)
        {'a': 3}
        >>> merge_dicts_custom({"a": [1, 2]}, {"a": [3]}, lambda x, y: x + y)
        {'a': [1, 2, 3]}
    """
    result = dict1.copy()
    
    for key, value in dict2.items():
        if key in result:
            result[key] = merge_func(result[key], value)
        else:
            result[key] = value
    
    return result

Примеры использования

# Базовый пример
print(merge_dicts({"a": 1, "b": 2}, {"b": 3, "c": 4}))
# {'a': 1, 'b': 5, 'c': 4}

# Объединение счётчиков
counts1 = {"apple": 5, "banana": 3}
counts2 = {"banana": 2, "orange": 4}
print(merge_dicts(counts1, counts2))
# {'apple': 5, 'banana': 5, 'orange': 4}

# Объединение статистики
stats1 = {"clicks": 100, "impressions": 500}
stats2 = {"clicks": 50, "conversions": 10}
print(merge_dicts(stats1, stats2))
# {'clicks': 150, 'impressions': 500, 'conversions': 10}

# Объединение с пользовательской функцией
data1 = {"x": [1, 2], "y": [3]}
data2 = {"x": [4], "y": [5, 6]}
result = merge_dicts_custom(data1, data2, lambda x, y: x + y)
print(result)
# {'x': [1, 2, 4], 'y': [3, 5, 6]}

# С использованием Counter
freq1 = {"a": 5, "b": 2, "c": 1}
freq2 = {"b": 3, "c": 4, "d": 2}
print(merge_dicts_counter(freq1, freq2))
# {'a': 5, 'b': 5, 'c': 5, 'd': 2}

Тестирование

def test_merge_dicts():
    # Базовые случаи
    assert merge_dicts({"a": 1, "b": 2}, {"b": 3, "c": 4}) == {"a": 1, "b": 5, "c": 4}
    
    # Без пересечений
    assert merge_dicts({"a": 1}, {"b": 2}) == {"a": 1, "b": 2}
    
    # Полное пересечение
    assert merge_dicts({"a": 1, "b": 2}, {"a": 3, "b": 4}) == {"a": 4, "b": 6}
    
    # Пустой словарь
    assert merge_dicts({}, {"a": 1}) == {"a": 1}
    assert merge_dicts({"a": 1}, {}) == {"a": 1}
    assert merge_dicts({}, {}) == {}
    
    # Отрицательные числа
    assert merge_dicts({"a": 5}, {"a": -3}) == {"a": 2}
    
    # Нулевые значения
    assert merge_dicts({"a": 0}, {"a": 5}) == {"a": 5}
    
    print("Все тесты пройдены!")

test_merge_dicts()

Сравнение производительности

import time
from collections import Counter

def benchmark_merge():
    dict1 = {f"key_{i}": i for i in range(1000)}
    dict2 = {f"key_{i}": i*2 for i in range(500, 1500)}
    
    # Простое решение
    start = time.time()
    for _ in range(1000):
        merge_dicts(dict1, dict2)
    simple_time = time.time() - start
    
    # С Counter
    start = time.time()
    for _ in range(1000):
        merge_dicts_counter(dict1, dict2)
    counter_time = time.time() - start
    
    print(f"Простое решение: {simple_time:.4f}s")
    print(f"Counter: {counter_time:.4f}s")

benchmark_merge()

Анализ сложности

Простое решение:

  • Временная сложность: O(n + m), где n и m — размеры словарей
  • Пространственная сложность: O(n + m) для нового словаря

Решение с Counter:

  • Временная сложность: O(n + m)
  • Пространственная сложность: O(n + m)

Оба решения эквивалентны по производительности. Выбирайте в зависимости от читаемости кода и требований к типам данных.

Практическое применение

  • Слияние логов: объединение счётчиков событий
  • Analytics: агрегация метрик по временным периодам
  • Инвентарь: объединение количеств товаров
  • Рейтинговые системы: слияние баллов пользователей
Объединение словарей с суммированием | PrepBro