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.