Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to access field in array of structs in Julia

Tags:

julia

struct Point
   x :: Int
   y :: Int
end

If I have an array a of type Array{Point}, is there a better way (either syntactically cleaner or faster) to do field access than this?

(p->p.x).(a)
like image 638
Adam Wheeler Avatar asked Oct 18 '17 22:10

Adam Wheeler


2 Answers

Four options are:

1) (p->p.x).(a)

2) [ z.x for z in a ]

3) map(p->p.x, a)

4) getfield.(a, x) (suggested by Michael in the comments)

The first 3 will be about as efficient as each other, so it comes down to personal preference. Method 4 is a bit slower on my machine, but as the other answerer (Gnimuc) states, this will hopefully be fixed up by issue #22710.

Note, I often also find the following method useful:

getx(a::Vector{Point}, inds=1:length(a))::Vector{Int} = [ a[i].x for i in inds ]

which allows you to pull out the x field for an arbitrary set of input indices. (although it will be slightly slower than the above 3 methods for pulling out every index). My metaprogramming sucks, but you can actually do something like this:

for fn in fieldnames(Point)
    eval(parse("get$(fn)(a::Vector{Point}, inds=1:length(a))::Vector{Int} = [ a[i].$(fn) for i in inds ]"))
end

which will get you the above getx function but for every fieldname in the input type...

like image 75
Colin T Bowers Avatar answered Nov 11 '22 20:11

Colin T Bowers


The cleanest way is to define your own operator which was originally posted by @pabloferz on Discourse: https://discourse.julialang.org/t/broadcast-over-getfield-in-0-6/2335/4

struct Point
   x :: Int
   y :: Int
end

a = [Point(i,j) for i = 1:10 for j = 1:10]

↦(val, s) = getfield(val, s)
a .↦ :x
a .↦ :y

UPDATE:

For now, a quick benchmark shows (p->p.x).(a) is the fastest among other solutions if a is small. when the length of a grows large, both map and comprehension are slightly faster than (p->p.x).(a):

julia> versioninfo()
Julia Version 0.6.0
Commit 903644385b* (2017-06-19 13:05 UTC)
......

julia> @btime (p->p.x).($a)
  88.283 ns (1 allocation: 896 bytes)

julia> @btime [ z.x for z in $a ]
  109.578 ns (2 allocations: 912 bytes)

julia> @btime map(p->p.x, $a)
  163.485 ns (3 allocations: 944 bytes)

julia> @btime getfield.($a,:x)
  1.586 μs (101 allocations: 4.00 KiB)

julia> a = [Point(i,j) for i = 1:100 for j = 1:100]

julia> @btime getfield.($a,:x);
  160.845 μs (10002 allocations: 390.70 KiB)

julia> @btime (p->p.x).($a);
  9.817 μs (2 allocations: 78.20 KiB)

julia> @btime map(p->p.x, $a);
  8.306 μs (3 allocations: 78.22 KiB)

julia> @btime [ z.x for z in $a ];
  8.306 μs (3 allocations: 78.22 KiB)

getfield is always 10~20x slower than other methods, so the cleanest way is not performant. But it seems that the situation is going to be improved in the future, we'll have a syntax sugar for this?: Make .a syntactic sugar for i->i.a #22710.

like image 5
Gnimuc Avatar answered Nov 11 '22 20:11

Gnimuc