Skip to main content

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

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))

# Step 1: Outer zip pairs corresponding inner tuples
outer_pairs = list(zip(t1, t2))
print(outer_pairs)
# [((1, 2), (5, 6)), ((3, 4), (7, 8))]

# Step 2: Inner zip pairs corresponding elements
for inner1, inner2 in zip(t1, t2):
element_pairs = list(zip(inner1, inner2))
print(element_pairs)
# [(1, 5), (2, 6)]
# [(3, 7), (4, 8)]

# Step 3: Add paired elements
for inner1, inner2 in zip(t1, t2):
sums = [a + b for a, b in zip(inner1, inner2)]
print(sums)
# [6, 8]
# [10, 12]

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)
)

# Usage
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))

# Works with deeper nesting
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)
# (((11, 22), (33, 44)), ((55, 66), (77, 88)))
tip

The recursive approach automatically handles any nesting level, making it ideal when your data structure depth varies.

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))

# Convert to arrays, add, convert back
arr1 = np.array(t1)
arr2 = np.array(t2)

result = tuple(map(tuple, (arr1 + arr2).tolist()))
print(result)
# ((6, 8), (10, 12))

NumPy with Larger Matrices​

import numpy as np

# Larger nested tuples
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))
note

NumPy conversion adds overhead for small tuples. The performance benefit becomes significant with matrices larger than approximately 100×100 elements.

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)
# ((11, 22, 3), (34, 45, 50), (60, 70))

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))

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

# NumPy approach
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​

MethodBest ForDependenciesHandles Variable Depth
Nested zipSmall to medium dataNone❌
Recursive functionVariable nesting depthNone✅
NumPyLarge matricesnumpy❌
reduceMultiple tuplesNone❌

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.