How to do "((1,2), 3) .+ ((1,2), 3)"?

TL;DR: How can we “broadcast” a function over several nested Tuples such as ((1,2), 3) .+ ((1,2), 3)?

So, say I have several objects x_i that are tuples of tuples and numbers (each of the tuples itself a tuple of tuples and numbers). For example x_1 = ((1,2), 3) or x_1 = (1, (2,3,(3,4)), (2,3)), etc… Now assume all x_i have the same structure, just different numbers. How can we apply a function f elementwise recursively, i.e. to arbitrary Tuple depth to a collection of x_i?

Currently ((1,2), 3) .+ ((1,2), 3) fails because it does + only at the first level, and tuples themselves cannot be summed. I know broadcast is what it is, and it’s not exactly this, so I’m looking for something different. I’d like it to yield ((1+1, 2+2), 3+3) etc. Is this possible? Can we make it work it with arbitrary tuple nesting?

Hopefully the question is clear, please ask otherwise.

If you’re doing math with tuples, consider using the StaticArrays packages instead. It gives you immutable arrays with arithmetic and linear algebra defined on them.

That said:

_f(x, y) = x + y
_f(x::Tuple, y::Tuple) = _f.(x, y)
julia> t1 = ((1,2), (3, (5,6)), (7, 8), 9);

julia> _f(t1, t1)
((2, 4), (6, (10, 12)), (14, 16), 18)

For numbers and Tuples of numbers, the above will work. Use at your own risk!

3 Likes

Here’s a crazy hack:

julia> ⊕(x, y) = x .⊕ y
       ⊕(x::Number, y::Number) = x + y
⊕ (generic function with 2 methods)

julia> ((1, 2), 3) .⊕ ((4, 5), 6)
((5, 7), 9)

It may behave suprisingly with unmatched tuple lengths and will stack-overflow with non-number-containing things.

4 Likes

Thanks! Yeah, I was rather looking for a solution more like broadcast or map that does not require to touch the methods of the function f. But I guess these options are probably the simplest

If you only need to go down one level, you could just broadcast a broadcasted operator. Hopefully someday soon you’ll be able to do (.+).(((1, 2), 3), ((4, 5), 6)) or broadcast(.+, ((1, 2), 3), ((4, 5), 6)), but until then you could do:

julia> broadcast((args...)->(+).(args...), ((1, 2), 3), ((4, 5), 6))
((5, 7), 9)
4 Likes

Very nice! I think I’ll mark @tomerarnon’s reply as solution, thanks again to both.

1 Like