Skip to content

How to Perform Element-wise Addition of Nested Tuples in Python

Learn efficient Python methods for adding corresponding elements in nested tuples like ((1,2), (3,4)) and ((5,6), (7,8)), including recursive approaches, NumPy optimization, and handling mismatched sizes.

Adding corresponding elements across nested tuples is a common operation in mathematical computing and data transformation. This guide covers efficient approaches for combining structures like ((1,2), (3,4)) and ((5,6), (7,8)) into ((6,8), (10,12)).

Using Nested Generators with zip

The most Pythonic approach uses nested generator expressions with zip to pair corresponding elements:

t1 = ((1, 2), (3, 4))
t2 = ((5, 6), (7, 8))

result = tuple(
    tuple(a + b for a, b in zip(inner1, inner2))
    for inner1, inner2 in zip(t1, t2)
)

print(result)  # ((6, 8), (10, 12))

Breaking Down the Solution

Understanding each layer helps clarify the logic:

t1 = ((1, 2), (3, 4))
t2 = ((5, 6), (7, 8))

outer_pairs = list(zip(t1, t2))
print(outer_pairs)

for inner1, inner2 in zip(t1, t2):
    element_pairs = list(zip(inner1, inner2))
    print(element_pairs)

for inner1, inner2 in zip(t1, t2):
    sums = [a + b for a, b in zip(inner1, inner2)]
    print(sums)

Creating a Reusable Function

Wrap the logic in a function with support for arbitrary nesting depth:

def add_nested_tuples(t1, t2):
    """Add corresponding elements of two nested tuples."""
    return tuple(
        tuple(a + b for a, b in zip(inner1, inner2))
        for inner1, inner2 in zip(t1, t2)
    )

result = add_nested_tuples(
    ((1, 2), (3, 4)),
    ((5, 6), (7, 8))
)
print(result)  # ((6, 8), (10, 12))

Handling Arbitrary Depth with Recursion

For deeply nested structures:

def add_tuples_recursive(t1, t2):
    """Recursively add nested tuples of any depth."""
    if isinstance(t1[0], tuple):
        return tuple(
            add_tuples_recursive(inner1, inner2)
            for inner1, inner2 in zip(t1, t2)
        )
    return tuple(a + b for a, b in zip(t1, t2))

deep1 = (((1, 2), (3, 4)), ((5, 6), (7, 8)))
deep2 = (((10, 20), (30, 40)), ((50, 60), (70, 80)))

result = add_tuples_recursive(deep1, deep2)
print(result)

Using NumPy for Large Data

When working with large matrices or numerical computations, NumPy provides optimized performance:

import numpy as np

t1 = ((1, 2), (3, 4))
t2 = ((5, 6), (7, 8))

arr1 = np.array(t1)
arr2 = np.array(t2)

result = tuple(map(tuple, (arr1 + arr2).tolist()))
print(result)  

NumPy with Larger Matrices

import numpy as np

t1 = tuple(tuple(range(i, i + 5)) for i in range(0, 20, 5))
t2 = tuple(tuple(range(100, 105)) for _ in range(4))

arr1 = np.array(t1)
arr2 = np.array(t2)

result = tuple(map(tuple, (arr1 + arr2).tolist()))

print("Shape:", arr1.shape)
print("Result:", result)

Output

Shape: (4, 5)
Result: ((100, 102, 104, 106, 108), (105, 107, 109, 111, 113), (110, 112, 114, 116, 118), (115, 117, 119, 121, 123))

Adding Multiple Tuples

Extend the approach to sum more than two nested tuples:

def add_multiple_nested_tuples(*tuples):
    """Add corresponding elements across multiple nested tuples."""
    return tuple(
        tuple(sum(values) for values in zip(*inners))
        for inners in zip(*tuples)
    )

t1 = ((1, 2), (3, 4))
t2 = ((5, 6), (7, 8))
t3 = ((10, 20), (30, 40))

result = add_multiple_nested_tuples(t1, t2, t3)
print(result)  # ((16, 28), (40, 52))

Using functools.reduce

For a functional programming approach:

from functools import reduce

def add_two_nested(t1, t2):
    return tuple(
        tuple(a + b for a, b in zip(i1, i2))
        for i1, i2 in zip(t1, t2)
    )

tuples = [
    ((1, 2), (3, 4)),
    ((5, 6), (7, 8)),
    ((10, 20), (30, 40))
]

result = reduce(add_two_nested, tuples)
print(result)  # ((16, 28), (40, 52))

Handling Mismatched Sizes

When tuples may have different lengths, decide on a strategy:

from itertools import zip_longest

def add_nested_with_fill(t1, t2, fillvalue=0):
    """Add nested tuples, treating missing elements as fillvalue."""
    return tuple(
        tuple(
            (a or fillvalue) + (b or fillvalue)
            for a, b in zip_longest(inner1, inner2, fillvalue=fillvalue)
        )
        for inner1, inner2 in zip_longest(t1, t2, fillvalue=())
    )

t1 = ((1, 2, 3), (4, 5))
t2 = ((10, 20), (30, 40, 50), (60, 70))

result = add_nested_with_fill(t1, t2)
print(result)

Using operator.add with map

An alternative using the operator module:

from operator import add

t1 = ((1, 2), (3, 4))
t2 = ((5, 6), (7, 8))

result = tuple(
    tuple(map(add, inner1, inner2))
    for inner1, inner2 in zip(t1, t2)
)

print(result)  # ((6, 8), (10, 12))

Performance Comparison

import timeit
import numpy as np

t1 = tuple(tuple(range(100)) for _ in range(100))
t2 = tuple(tuple(range(100, 200)) for _ in range(100))

def python_add():
    return tuple(
        tuple(a + b for a, b in zip(i1, i2))
        for i1, i2 in zip(t1, t2)
    )

arr1, arr2 = np.array(t1), np.array(t2)
def numpy_add():
    return tuple(map(tuple, arr1 + arr2))

python_time = timeit.timeit(python_add, number=1000)
numpy_time = timeit.timeit(numpy_add, number=1000)

print(f"Pure Python: {python_time:.4f}s")
print(f"NumPy:       {numpy_time:.4f}s")

Method Comparison

Method Best For Dependencies Handles Variable Depth
Nested zip Small to medium data None ❌
Recursive function Variable nesting depth None ✅
NumPy Large matrices numpy ❌
reduce Multiple tuples None ❌

Summary

Use nested zip with generator expressions for straightforward element-wise addition of nested tuples: it's readable and requires no dependencies.

  • Switch to NumPy when processing large matrices where performance matters.
  • For deeply or variably nested structures, implement a recursive solution that adapts to any depth automatically.
Navigation