3

How to add a 2d tuple to a 2d matrix in Julia?

    t1 = ((10,20),(30,40)); #2d immutable tuple 
    a = [1 2;3 4] #2d matrix 
    a .+ t1 

throws an error:

MethodError: no method matching +(::Int64, ::Tuple{Int64, Int64})
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:560
  +(::T, ::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} at int.jl:87
  +(::Integer, ::Ptr) at pointer.jl:161
  ...
Stacktrace:
 [1] _broadcast_getindex_evalf
   @ .\broadcast.jl:648 [inlined]
 [2] _broadcast_getindex
   @ .\broadcast.jl:621 [inlined]
 [3] getindex
   @ .\broadcast.jl:575 [inlined]
 [4] copy
   @ .\broadcast.jl:922 [inlined]
 [5] materialize(bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{2}, Nothing, typeof(+), Tuple{Matrix{Int64}, Tuple{Tuple{Int64, Int64}, Tuple{Int64, Int64}}}})
   @ Base.Broadcast .\broadcast.jl:883
 [6] top-level scope
   @ REPL[15]:1

Is there a vector/matrix addition method exists? Obviously I can use a for loop for element by element addition.

Vinod
  • 4,138
  • 11
  • 49
  • 65

2 Answers2

7

Ah, so the problem here is that, while you call t1 a "2d tuple", it is really not; it is a nested tuple, a tuple-of-tuples, and is thus not really comparable to your 2d array (which really is a two-dimensional object and not just an array-of-arrays).

If you want to add a two-dimensional array to an immutable object that can be stack-allocated like a Tuple while being truly two-dimensional, then you can use the StaticArrays.jl package, which provides the immutable SArray type:

julia> a = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> using StaticArrays

julia> t1 = SA[10 20; 30 40]
2×2 SMatrix{2, 2, Int64, 4} with indices SOneTo(2)×SOneTo(2):
 10  20
 30  40

julia> a + t1
2×2 SMatrix{2, 2, Int64, 4} with indices SOneTo(2)×SOneTo(2):
 11  22
 33  44

However, if for any reason you really want to stick with nesting instead of multidimensionality, then let's make the array nested as well

julia> t1 = ((10,20),(30,40)) #tuple-of-tuples
((10, 20), (30, 40))

julia> a = [[1,2],[3,4]] #vector-of-vectors
2-element Vector{Vector{Int64}}:
 [1, 2]
 [3, 4]

in which case you can easily solve by recursion

add(a,b) = a + b
add(a::Vector, b::NTuple{N}) where N = [add(a[i],b[i]) for i in 1:N]
add(a::NTuple{N}, b::Vector) where N = [add(a[i],b[i]) for i in 1:N]
julia> add(a,t1)
2-element Vector{Vector{Int64}}:
 [11, 22]
 [33, 44]

This approach will also scale to arbitrarily deeply nested objects:

julia> a = [[[1,2],[3,4]],[[5,6],[7,8]]]
2-element Vector{Vector{Vector{Int64}}}:
 [[1, 2], [3, 4]]
 [[5, 6], [7, 8]]

julia> t1 = (((10,20),(30,40)),((50,60),(70,80)))
(((10, 20), (30, 40)), ((50, 60), (70, 80)))

julia> add(a,t1)
2-element Vector{Vector{Vector{Int64}}}:
 [[11, 22], [33, 44]]
 [[55, 66], [77, 88]]

Unfortunately, there is widespread semantic confusion between true multidimensional arrays and the common hack of using a vector-of-vectors in languages which do not support real multidimensional arrays.

cbk
  • 4,225
  • 6
  • 27
5

@cbk has already explained how nested arrays are not equivalent to multidimensional structures. But you can of course write your addition as a nested broadcast if your structure is already nested. Since (I belive) Julia 1.6, there is syntax for standalone "broadcasted operators", with which this is possible:

julia> t1 = ((10,20),(30,40))
((10, 20), (30, 40))

julia> a = [[1,2],[3,4]]
2-element Vector{Vector{Int64}}:
 [1, 2]
 [3, 4]

julia> broadcast(.+, a, t1)
2-element Vector{Vector{Int64}}:
 [11, 22]
 [33, 44]

If a is a Matrix, you can do the same on eachrow:

julia> a = [1 2;3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> broadcast(.+, eachrow(a), t1)
2-element Vector{Vector{Int64}}:
 [11, 22]
 [33, 44]

(In this specific case, map is equivalent to broadcast.)

phipsgabler
  • 20,535
  • 4
  • 40
  • 60
  • 1
    Oh, that's great! We might note though that OP will still have to transform `a` to a vector-of-vectors in this approach, else you get a different answer; ```julia> broadcast(.+, a, t1) 2×2 Matrix{Tuple{Int64, Int64}}: (11, 21) (12, 22) (33, 43) (34, 44) ``` – cbk Jan 21 '22 at 07:25
  • 1
    Ah, dang, of course. I copied `a` from your answer, but from the part where it already is a vector of vectors :D – phipsgabler Jan 21 '22 at 07:26
  • You can also write these as `(.+).(t1, a)` and `(.+).(t1, eachrow(a))`. (I thought I'd find them more intuitive written this way, but it turns out I don't; the `broadcast` call versions seem clearer. At least they look like some cute wonky emojis!) – Sundar R Jan 21 '22 at 08:22
  • 1
    Or `add_k(k) = foldr((f, _) -> Broadcast.BroadcastFunction(f), 1:k, init=+)` for general `K`-level nesting. – phipsgabler Jan 21 '22 at 08:29