Creation/Definition of global variable for each step of a loop

Is there a way to create/define a new variable for each step inside a loop?

Pseudocode:

for i in 1:3
     xi = i

end

The result of this loop would be x1 = 1, x2 = 2, and x3 = 3.

Thanks,

DS

This question comes up quite regularly, and the answer is usually “you shouldn’t”, see eg here Creating multiple variables with consecutive names using a for loop

If you give me background as to what you avidly want to achieve people can probably suggest a different approach.

9 Likes

If you decide after reading all the reasons not to do this, that you still want to know how to do it, I explain the ‘right’ way to do it here: How to use string substitution in variable names - #11 by Mason

5 Likes

Maybe a more general form of the above macro would be

julia> macro n_assignments(name::Symbol, n::Integer, f)
           Expr(:block, __source__, map(i -> esc(:($(Symbol(name, i)) = $f($i) )), 1:n)...)
       end

Looking at the code this produces:

julia> @macroexpand @n_assignments(x, 3, f)
quote
    #= REPL[9]:1 =#
    x1 = f(1)
    x2 = f(2)
    x3 = f(3)
end

so applied to your problem, you might write

julia> @n_assignments(x, 3, identity)
3

julia> x1
1

julia> x2
2

julia> x3
3

julia> x4
ERROR: UndefVarError: x4 not defined

Voila!

2 Likes

You probably just want a vector:

x = Int[]
for i in 1:3
  push!(x,i)
end

julia> x[1] 
1

3 Likes

Thanks for the responses! I think I have some learning to do…

With regards to my specific goal: I am constantly loading in various amounts of data (CSVs where the columns will become the variables of interest). Anyways, I need easy ways to access the data.

Here’s a better example (again this is pseudocode):

reference1 = [0.,1,1.3,1.2,1.5,2.5]

reference2 = [0.0,.1,.2,.3,.4,.5,.6,.7,.8,.9, 1.]


for i in reference1
    for j in reference2
        #temporary variable to store csv
        csv = CSV.read(string("warbler",i,"Species",j,".csv"))
        #create new global variables corresponding to csv loaded in and specific columns of csv:
        #(obviously this is not real code, but what I am looking to do)
        ages_ij = csv[:,1]
        weights_ij = csv[:,2]
        locs_ij = csv[:,3]
    end
end

while this is certainly a valid solution, you can also use a nested data structure to achieve the same:

CSV_ref = Dict(
"ages_$(i)$(j)" => CSV.read(string("warbler",i,"Species",j,".csv"))[:, 1]
for i in reference1, j in reference2
)

Later, CSV_ref["ages_12"]. (assuming i,j are nice integers, because you probably don’t want ages_0.23.4 as your variable name anyways)


In short I think people worry about having code modify variable bindings directly may result in headache later.

2 Likes

This seems like a really good solution. Thanks so much.

Yeah, it’s still not clear to me why it’s such a no-no, but it seems like it’s certainly the consensus.

because your files are separated by i, j. I would probably make a matrix of CSV tables and access stuff with:

csvs[i, j]["ages"]

but yeah, you can do whatever fits your need. Making a bunch of columns scattered as individual variables sound messy.

2 Likes

The reason it’s a no-no is that it’s either inflexible, or requires runtime code generation.

Macros are nice and efficient (because they are expanded at parse time), but it seems they are not appropriate for your usecase here because of the reason they’re efficient: you need to know how to expand a macro based on just the representation of the code, not the value of anything. For instance, with my above macro:

julia> let n = 10
           @n_assignments(x, n, identity)
       end
ERROR: LoadError: MethodError: no method matching var"@n_assignments"(::LineNumberNode, ::Module, ::Symbol, ::Symbol, ::Symbol)
Closest candidates are:
  var"@n_assignments"(::LineNumberNode, ::Module, ::Symbol, ::Integer, ::Any) at REPL[28]:1
in expression starting at REPL[29]:2

Why did this happen? Because the n there is not equivalent to putting the number 10 in, even though at runtime, n=10. Here’s a macro that perhaps can make this difference clear:

julia> macro explainer(x)
           println("this is happening at macroexpansion time. The value of x is $(repr(x))")
           quote
               println("this is happening at run time. The value of x is $(repr($x))")
           end |> esc
       end
@explainer (macro with 1 method)

now, let’s stick this macro in a function that we haven’t called yet:

julia> f(x) = @explainer x
this is happening at macroexpansion time. The value of x is :x
f (generic function with 1 method)

it ran the first println call before we ever gave f a value! This means it’s before x ever meant anything. Now let’s run f:

julia> f(1)
this is happening at run time. The value of x is 1

julia> f(100//1)
this is happening at run time. The value of x is 100//1

Because of this property, a macro is not appropriate for your pseudocode:

for i in reference1
    for j in reference2
        #temporary variable to store csv
        csv = CSV.read(string("warbler",i,"Species",j,".csv"))
        #create new global variables corresponding to csv loaded in and specific columns of csv:
        #(obviously this is not real code, but what I am looking to do)
        ages_ij = csv[:,1]
        weights_ij = csv[:,2]
        locs_ij = csv[:,3]
    end
end

because you want to create a number of variables that depends on runtime information.


This means your only recourse is runtime code generation with something like eval, but eval is slow, brittle and easy to misuse because it has to occur in the global scope (locally scoped eval would make it impossible for julia to generate efficient code, and we’d basically be another Python or R if we had it).

On the other hand, a datastructure like an array or a dictionary is perfectly suited to this task, so I’d strongly suggest using those.

4 Likes

This is the most simple solution:

If this works for you ok, but that is not the optimal way to store the data from a performance point of view. If your analysis take most than you can accept you’ll need to learn other data structure.

1 Like

This is a really amazing explanation! Thank you for taking the time.

1 Like