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
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.
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
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!
You probably just want a vector:
x = Int[]
for i in 1:3
push!(x,i)
end
julia> x[1]
1
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.
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.
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.
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.
This is a really amazing explanation! Thank you for taking the time.