Search code examples
arraysdictionaryjuliavectorizationbroadcast

Broadcasting over different sized arrays


Say I have the following function

function foo(p :: Int, x :: Real)
  return p*x
end

And I want to call it for the following arrays:

P = [1, 2, 3]
X = [5.0, 6.0, 7.0, 8.0]

If I want to call foo and store the value in a single array of size (length(P), length(X)) I could just double loop X and P as:

R = zeros(length(P), length(X))
for i in 1:length(P), j in 1:length(X)
  R[i, j] = foo(P[i], X[j])
end

However, is there another way to do this while explicitly avoiding the double loop? Maybe something like?

R = foo.(collect(zip(P, X)))

Of course, the above does not work as foo cannot handle ::Tuple{Int64, Float64}, but I have not found a way to broadcast over different sized arrays for the moment. Any tips would be appreciated, thanks!


Solution

  • If you want to use broadcasting you can do it like this:

    julia> foo.(P, permutedims(X))
    3×4 Matrix{Float64}:
      5.0   6.0   7.0   8.0
     10.0  12.0  14.0  16.0
     15.0  18.0  21.0  24.0
    

    or

    julia> foo.(P, reshape(X, 1, :))
    3×4 Matrix{Float64}:
      5.0   6.0   7.0   8.0
     10.0  12.0  14.0  16.0
     15.0  18.0  21.0  24.0
    

    or

    julia> (((x, y),) -> foo(x, y)).(Iterators.product(P, X))
    3×4 Matrix{Float64}:
      5.0   6.0   7.0   8.0
     10.0  12.0  14.0  16.0
     15.0  18.0  21.0  24.0
    

    or

    julia> Base.splat(foo).(Iterators.product(P, X))
    3×4 Matrix{Float64}:
      5.0   6.0   7.0   8.0
     10.0  12.0  14.0  16.0
     15.0  18.0  21.0  24.0
    

    Note that adjoint (') will not work here in general, as it is recursive:

    julia> x = ["a", "b"]
    2-element Vector{String}:
     "a"
     "b"
    
    julia> permutedims(x)
    1×2 Matrix{String}:
     "a"  "b"
    
    julia> x'
    1×2 adjoint(::Vector{String}) with eltype Union{}:
    Error showing value of type LinearAlgebra.Adjoint{Union{}, Vector{String}}:
    ERROR: MethodError: no method matching adjoint(::String)