4

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?

Conor
  • 691
  • 5
  • 14

1 Answers1

3

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]
Bogumił Kamiński
  • 66,844
  • 3
  • 80
  • 107
  • Great, thank you. Actually the 'interesting case' you outlined in the second part of your answer, is what I originally meant with the question! Essentially, I want to get the 'average `:x` ' across the different instances of the struct, where you create a single new variable whose individual elements are the sums/averages of the corresponding elements in each of the `:x` fields of `A`. This is exactly what's achieved with your `sum(getproperty.(my_array,:x))` – Conor Jul 08 '20 at 18:26
  • 2
    Ah - then a general scheme is that with `getproperty.(my_array, :x)` you extract out an array of arrays. Then - how to work with this array of arrays depends on what API the function you want to apply provides. I will add a comment on a general scheme in this case in the answer. – Bogumił Kamiński Jul 08 '20 at 18:46