Search code examples
structtypesjuliaarray-broadcastingbroadcasting

Julia: applying function across fields of different struct instances


Let's imagine I have an array my_array whose elements are instances of myType, so the array type is given by Array{myType,1}.

Now let's say each instance of myType within my_array has some field x that stores a vector of data (e.g. typeof(instance_of_myType.x) == Array{Float32,1}).

If I know the different fields x of each instance of the myType struct in the Array have the same size, is there a way I can broadcast a function (e.g. averaging or summing) across the different fields x of all the instances of myType in the array, that will give me a single Array{Float32,1} vector, that is the resulting of applying the function to all the x fields of the structs within my_array? Is there a way to do this without a for-loop, is basically what I'm asking?


Solution

  • This should work (unless you have overridden getproperty for your type):

    sum.(getproperty.(my_array, :x))
    

    Here is a full example comparing broadcasting and using a comprehension:

    julia> struct A{T}
                  x::T
                  end
    
    julia> my_array = [A(collect(i:i+3)) for i in 1:3]
    3-element Array{A{Array{Int64,1}},1}:
     A{Array{Int64,1}}([1, 2, 3, 4])
     A{Array{Int64,1}}([2, 3, 4, 5])
     A{Array{Int64,1}}([3, 4, 5, 6])
    
    julia> sum.(getproperty.(my_array, :x))
    3-element Array{Int64,1}:
     10
     14
     18
    
    julia> [sum(v.x) for v in my_array]
    3-element Array{Int64,1}:
     10
     14
     18
    

    Now an interesting case is if you wanted to apply a function e.g. sum across individual elements of fields x in the structs. You could get this result like this:

    julia> sum(getproperty.(my_array, :x))
    4-element Array{Int64,1}:
      6
      9
     12
     15
    

    (note that the only difference in this case is that there is no . after sum)

    or like this

    julia> sum(v -> v.x, my_array)
    4-element Array{Int64,1}:
      6
      9
     12
     15
    

    EDIT

    So a general approach would be:

    julia> tmp = getproperty.(my_array, :x)
    3-element Array{Array{Int64,1},1}:
     [1, 2, 3, 4]
     [2, 3, 4, 5]
     [3, 4, 5, 6]
    

    and now you can write:

     [fun(getindex.(tmp, i)) for i in eachindex(tmp...)]
    

    assuming that fun takes a vector as an argument.

    If you wanted to use an excellent SplitApplyCombine.jl package you could write:

    fun.(invert(getproperty.(my_array, :x)))
    

    as invert function does exactly what you need, e.g.:

      julia> invert([[1,2,3], [4,5,6]])
      3-element Array{Array{Int64,1},1}:
       [1, 4]
       [2, 5]
       [3, 6]