Idiomatic way to write element-wise combination of array of arrays?

question

#1

I was wondering how others would construct a combination of the product of two nested arrays?

Example and my approach:

a = [[1,2,3],[2,3,4]]  
b = [[1,1,1],[0,0,0]] 

# want [[1,2,3],[0,0,0]]

z = []
for (i,x) in enumerate(a)
    z = append!(z,[x .* b[i]])
end
z 

returns:

2-element Array{Any,1}:
 [1, 2, 3]
 [0, 0, 0]

I feel like my approach is a little clunky


#2

Multiple dispatch is pretty cool:

 import Base.*

*(x::Array{Array{Int64,1},1}, y::Array{Array{Int64,1},1}) = [x_k .* y_k for (x_k,y_k) in zip(x,y)]

a = [[1,2,3],[2,3,4]]  
b = [[1,1,1],[0,0,0]] 

a*b
2-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [0, 0, 0]

#3

How about

julia> broadcast.(*, a, b)
2-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [0, 0, 0]

#4

This is also possible.

julia> ((x,y)->x.*y).(a,b)
2-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [0, 0, 0]

#5

Or define a broadcastable *₊ function like this:

julia> *₊(a, b) = a .* b
*₊ (generic function with 1 method)

julia> a .*₊ b
2-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [0, 0, 0]

#6

Note that my solution generalize well to arbitrary “depths”:

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

julia> ab = [a, b]
2-element Array{Array{Array{Int64,1},1},1}:
 [[1, 2, 3], [2, 3, 4]]
 [[1, 1, 1], [0, 0, 0]]

julia> broadcast.(broadcast, *, aa, ab)
2-element Array{Array{Array{Int64,1},1},1}:
 [[1, 4, 9], [4, 9, 16]]
 [[1, 2, 3], [0, 0, 0]]

#7

Random thought:
Could it be possible to define (f.) to be a function such that (f.)(args...; kwargs...) = f.(args...; kwargs...)? This way for nested broadcast one would do (f.).(args...; kwargs...)

I’m completely ignorant about the broadcast implementation and its subtleties, so this may be complete nonsense, but I became curious as the issue of multiplying nested arrays has been coming up occasionally.


#8

Random thought:
Could it be possible to define (f.) to be a function such that (f.)(args...; kwargs...) = f.(args...; kwargs...) ? This way for nested broadcast one would do (f.).(args...; kwargs...)

This came up recently on Slack and @StefanKarpinski thought it was a possibility.


#9

If you want a shorthand notation, maybe by abusing the exponentiation/power-like symbol…

julia> ↑¹(f, args) = broadcast(f, args...)
       ↑²(f, args) = ↑¹(broadcast, (f, args...))
       ↑³(f, args) = ↑²(broadcast, (f, args...));

julia> div↑¹([1], [2])
1-element Array{Int64,1}:
 0

julia> div↑²(b, a)
2-element Array{Array{Int64,1},1}:
 [1, 0, 0]
 [0, 0, 0]

julia> div↑³(ab, aa)
2-element Array{Array{Array{Int64,1},1},1}:
 [[1, 1, 1], [1, 1, 1]]
 [[1, 0, 0], [0, 0, 0]]

#10

More generally, you can recursively define this broadcast up to any depth n like this

julia> function rebroadcast(f,n,args...)
           n>1 ? rebroadcast(broadcast,n-1,(f,args...)) : broadcast(f, args...)
       end
rebroadcast (generic function with 1 method)

julia> rebroadcast(div,1,[1],[2])
1-element Array{Int64,1}:
 0

julia> rebroadcast(div,2,b,a)
2-element Array{Array{Int64,1},1}:
 [1, 0, 0]
 [0, 0, 0]

julia> rebroadcast(div,3,ab,aa)
2-element Array{Array{Array{Int64,1},1},1}:
 [[1, 1, 1], [1, 1, 1]]
 [[1, 0, 0], [0, 0, 0]]