4

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.

dapias
  • 2,512
  • 3
  • 15
  • 23
  • 2
    Are you really sure you want to do this? In most cases it would be better to build an array doing something like `k = []; for val in values(d) push!(k, val) end`, and then access the values as `k[n]` instead of `kn`. Scratch that, you can do `k = collect(values(d))`. – DNF Nov 14 '17 at 07:44
  • Well, actually I am not pretty sure I want to do this in a real program: it would imply too much variables in the namespace. Your tip is good if I needed to iterate the whole dictionary but recall I am just interested in certain keys. – dapias Nov 14 '17 at 08:02
  • 1
    Then: `k = collect(Iterators.take(values(d), 10))`? – DNF Nov 14 '17 at 08:05
  • Oh nice! This is good. But what if the keys I want to extract are not arranged as the first ten keys, rather they are separated by other keys. – dapias Nov 14 '17 at 08:11
  • 2
    Index into the dict by building keys like this: `for i in 1:10 key = Symbol('S', i); push!(k, d[key]); end`. – DNF Nov 14 '17 at 08:14
  • Ok, this seems to work as an alternative way to the definition of new variables. Perhaps it is better to proceed in this way. Thanks @DNF – dapias Nov 14 '17 at 09:45

3 Answers3

8

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.

Chris Rackauckas
  • 18,645
  • 3
  • 50
  • 81
4

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.

Frames Catherine White
  • 27,368
  • 21
  • 87
  • 137
  • 1
    As far as I've heard, `eval` is _not_ your friend. Most likely, it's the answer to the wrong question. – DNF Nov 14 '17 at 07:34
  • 2
    well if not your friend then at least `eval` is 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) – Frames Catherine White Nov 14 '17 at 07:38
  • 1
    Possibly better would be to do something like [DataFramesMeta.jl's](https://github.com/JuliaStats/DataFramesMeta.jl) `@with` – Frames Catherine White Nov 14 '17 at 07:42
  • Nice answer. Well, I am not an expert but `eval` looks like a friend. I just forgot to say that I did not want to build a variable for all the keys in the dictionary. Perhaps the answer of @ChrisRackauckas is the correct one for this case. Just not sure about the efficiency – dapias Nov 14 '17 at 09:43
  • @LyndonWhite: do you think this answer can be adapted for the case of selecting the special keys `:S1 ... :S10`? – dapias Nov 14 '17 at 09:50
  • 1
    If the keys you want are known at compile time use (or adapt) @ChrisRackauckas answer. It is much preferable. It will be at least as fast. Adapting this for selecting special keys is just a matter of using an `if` etc in the loop. I'll edit the answer. – Frames Catherine White Nov 14 '17 at 09:54
3

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.

Liso
  • 2,165
  • 1
  • 16
  • 19
  • This answer is nice. Thank you! I just forgot to say that I did not want to do it for all the keys in the dict. – dapias Nov 14 '17 at 09:40