How to use string substitution in variable names

I would like to do something like the following but that doesn’t work. How can I do this? Maybe use $ somehow? Edit: To be clear, I am trying to create the first 7 unit vectors. Thanks

for i in 1:7
    e_i = zeros(7)
    e_i[i] = 1
end

What result do you want to get? Did you want the following?

e_i = zeros(7)
for i in 1:7
    e_i[i] = 1
end
1 Like

very clumsy, but I guess you look for something like:

julia> for i in 1:7
           eval(Meta.parse("e_$i = zeros(7)"))
           eval(Meta.parse("e_$i[$i] = 1"))
       end

julia> show(e_5)
[0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]
4 Likes

I was trying to create unit vectors.

Don’t use metaprogramming or generated variable names. Use a data structure. For example, an array e of the 7 unit vectors in ℝ⁷ can be constructed with:

e = [ let v = zeros(7); v[i] = 1; v; end for i in 1:7 ]

This way, you can refer to e[i] programmatically rather than (as you seem to want) having 7 separate variables e_1, e_2, …, e_7. For example:

julia> e[3]
7-element Array{Float64,1}:
 0.0
 0.0
 1.0
 0.0
 0.0
 0.0
 0.0

See also this thread: How to warn new users away from metaprogramming

10 Likes

It’s not fully transparent to me what is going in on in

e = [ let v = zeros(7); v[i] = 1; v; end for i in 1:7 ].

I looked up let so have a little feel for that, but I don’t understand how to think about the v; part. I notice that if I take it out, I just get a single vector of all ones instead of an array of 7 unit vectors/arrays.

Here is a more transparent, but less performant, way of constructing the same object.

e = Vector{Float64}[]
for i in 1:7
    v = zeros(7)
    v[i] = 1
    push!(e, v)
end
1 Like

I assume you meant v[i] =1 not v[1] =1 but I understand that thanks everyone.

Thank you. Fixed.

1 Like

The let block needs to return the value that you want to store in the array formed by the comprehension. Since the return value of a let block is the value of the last statement, you have to put v as the last statement to store the vector v in the result array.

In contrast, if the last statement in the let block is v[i] = 1, the value of this statement is 1 (e.g. x = v[i] = 1 assigns 1 to both x and v[i]), so the comprehension returns an array of 1's.

2 Likes

While I agree that this is perhaps not an appropriate place for using meta-programming, I beleive it’s still useful to also answer the question that was asked. Since the only answer here that attempts to address the actual question asked uses eval, I want to note that something like this can be done very cleanly without eval, and is actually an important part of julia’s Cartesian indexing implementation.

The way to do this when the number of unit vectors needed is known at macroexpansion time is to write a macro. Here’s one possible way:

julia> macro define_basis_vecs(sym::Symbol, n::Integer)
           Expr(:block, __source__, map(i -> :($(esc(Symbol(sym, i))) = $( [j==i for j in 1:n] )), 1:n)...)
       end
@define_basis_vecs (macro with 1 method)

We can macroexpand it to make sure it generates the right code:

julia> @macroexpand @define_basis_vecs e_ 5
quote
    #= REPL[15]:1 =#
    e_1 = Bool[1, 0, 0, 0, 0]
    e_2 = Bool[0, 1, 0, 0, 0]
    e_3 = Bool[0, 0, 1, 0, 0]
    e_4 = Bool[0, 0, 0, 1, 0]
    e_5 = Bool[0, 0, 0, 0, 1]
end

Unlike solutions with eval, this will work in the local scope.

julia> let 
           @define_basis_vecs e_ 7
           e_1 * e_7'
       end
7×7 BitMatrix:
 0  0  0  0  0  0  1
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0

and won’t leak any variables out of that scope:

julia> e_1
ERROR: UndefVarError: e_1 not defined

But again, as was pointed out earlier, this is a usecase that is usually better satisfied in much more ‘boring’ ways without any metaprogramming.

If you’ve spoken to your doctor and you think that Metaprogramming may be right for you, I’d recommend reading this section of the manual thoroughly: Metaprogramming · The Julia Language

12 Likes

In the context of this problem, I would suggest finding another doctor.

6 Likes

@stevengj, assuming LinearAlgebra is being used, one could also write the 7 unit vectors in ℝ⁷ compactly with:

e = [diagm(ones(7))[i,:] for i in 1:7]

Faster an shorter:

collect(eachcol(diagm(ones(7))))
2 Likes

@DNF, quoting you in this other post " Why are you using collect ? :grimacing: Never use collect (If I ever get a tattoo, it will say that)"
Cheers :slight_smile:

NB: one could also use splatting:

e = [eachcol(diagm(ones(7)))...]

I am so aware :wink: But I wouldn’t do it if I were to iterate over them.

2 Likes