Efficient way to define new variables by iterating a dictionary (Julia)

Viewed 983

Consider a dictionary d in Julia which contains a thousand of keys. Each key is a symbol and each value is an array. I can access to the value associated with the symbol :S1 and assign it to the variable k1 via

k1 = d[:S1]

Now assume I want to define the new variables k2, k3, k4, ..., k10 by repeating the same procedure for the special keys :S1 ... :S10 (not for all the keys in the dictionary). What is the most efficient way to do it? I have the impression this can be solved using metaprogramming but not sure about that.

3 Answers

The easy way is to use Parameters.jl.

using Parameters
d = Dict{Symbol,Any}(:a=>5.0,:b=>2,:c=>"Hi!")
@unpack a, c = d
a == 5.0 #true
c == "Hi!" #true

BTW, this doesn't use eval.

If the special keys are all known at compile time, I suggest using Chris Rackauckas's answer It is much less evil and works to create local variables.

If for some reason they are only known at runtime, then you can do as follows. (Though I guess it is actually pretty strange to need to create variables who's name you don't even know at compile time)

@eval is your friend* here. See the manual @eval $key = $value

Or you can use the functional form eval() taking a quoted expression: eval(:($key = $value))

Note however you can not use this to introduce new local variables. eval always executes at module scope. And that is a intentional restriction for performance reasons

julia> d = Dict(k => rand(3) for k in [:a, :b1, :c2, :c1])
Dict{Symbol,Array{Float64,1}} with 4 entries:
  :a  => [0.446723, 0.0853543, 0.476118]
  :b1 => [0.212369, 0.846363, 0.854601]
  :c1 => [0.542332, 0.885369, 0.635742]
  :c2 => [0.118641, 0.987508, 0.578754]

julia> for (k,v) in d
          #create constants for only ones starting with `c`
          if first(string(k)) == c 
              @eval const $k = $v
          end
       end


julia> c2
3-element Array{Float64,1}:
  0.118641
  0.987508
  0.578754

*Honestly eval is not your friend. It is however the only dude badass enough to walk with you down this dark road of generating code based on runtime values. (@generate is only marginally less badass being willing to generate code based on runtime Types). If you are in this situation where you absolutely have to generate code based on runtime information consider whether you have not made a design mistake several forks further up the road.

In case you really want to have k1, k2, ... , k10, ... you could use a little more complicated eval than Lyndon's:

for (i,j) in enumerate(d)
       @eval $(Symbol("k$i")) = $j.second
end

Warning: eval() use global scope so even if you use this inside a function k1...kn will be global variables.

Related