变量参数化, 循环处理变量名称

想到一个问题, 比如我想将100个向量合并, 他们的名字分别取为a1,a2, …, a100, 理论上来说, 我可以将变量作为1-100循环的, 然后与字母a拼起来整体作为变量, 利用eval()来提取, 从那么这个在julia中该如何实现呢?matlab中直接``[ ]’'拼起来就可以了, julia必然可以实现,但我一下想不到了。

啥玩样,没看懂,要不你贴个伪代码吧

比如这样, 以三个为例, 这三个向量的构造各不相同,那么有
vec_1 = [1 2 3]
vec_2 = [1 5 7]
vec_3 = [2 6 9]
然后需要构建一个Vec, 使得
Vec = [vec_1’; vec_2’; vec_3’]=[1 2 3; 1 5 7; 2 6 9]
但如果我现在vec的个数比较多,如100个,而且个数可能是变化的, 即100,102等.
需要根据n,来循环合并
for i=1:3
global Vec = [Vec; vec_i’] # 问题在这里的vec_i的写法
end
我想,这个问题现在通俗易懂了吧

julia> vec_1 = [1 2 3]
1×3 Array{Int64,2}:
 1  2  3

julia> vec_2 = [1 5 7]
1×3 Array{Int64,2}:
 1  5  7

julia> vec_3 = [2 6 9]
1×3 Array{Int64,2}:
 2  6  9

julia> vec=Int[]
0-element Array{Int64,1}

julia> for i in 1:3
       global vec=[vec;eval(Symbol("vec_"*"$i"))']
       end
julia> vec
9×1 Array{Int64,2}:
 1
 2
 3
 1
 5
 7
 2
 6
 9

你可以用 eval(Symbol("vec_"*"$i")) 的形式

1 个赞

哦哦,julia用``*’'连接,我都忘了,非常感谢

也可以用@ntuple做

julia> using Base.Cartesian

julia> vec_1 = [1 2 3]
1×3 Array{Int64,2}:
 1  2  3

julia> vec_2 = [1 5 7]
1×3 Array{Int64,2}:
 1  5  7

julia> vec_3 = [2 6 9]
1×3 Array{Int64,2}:
 2  6  9

julia> Vec = vcat((@ntuple 3 vec)...)
3×3 Array{Int64,2}:
 1  2  3
 1  5  7
 2  6  9
2 个赞

这个有点儿妙, 这里变量刚好是``_i’'的形式. 请问如果是vec1或者vec_1这类, 是不是也可以适当调整一下形式,也写得这么优雅?

如果是 @nebula 的方法,好像不行,但是这样可以

julia> vcat((eval(Symbol("vec$i")) for i in 1:3)...)
3×3 Array{Int64,2}:
1  2  3
1  5  7
2  6  9
2 个赞

nice,这样就方便多了

Base.Cartesian里有一系列@n开头的宏,展开后都是变量名+“_i”的形式(i从1开始计数)。vec_1这样的变量是能够写成这样的形式,但是vec1这样的就不行。vec1这样的变量命名需要重新写一个新的宏来实现。
下面是@ntuple的实现

macro ntuple(N::Int, ex)
    vars = Any[ inlineanonymous(ex,i) for i = 1:N ]
    Expr(:escape, Expr(:tuple, vars...))
end

# Given :i and 3, this generates :i_3
inlineanonymous(base::Symbol, ext) = Symbol(base,'_',ext)

你可以把_换成其他符合语法的字符来实现,这里你可以把Symbol(base,’_’,ext)改为Symbol(base,ext)来实现vec1这样的命名方式。

1 个赞

好的,这个问题我想我已经学到了很好的解决办法。刚才写了一个小测试, 又发现是这样, 这个作用域问题是什么情况?

julia> function test_()
       vec1 = [1 2 3]
       vec2 = [1 5 7]
       vec3 = [2 6 9]
       print(vcat((eval(Symbol("vec$i")) for i in 1:3)...))
       end
test_ (generic function with 1 method)
Julia> @time(test_())
ERROR: UndefVarError: vec1 not defined
Stacktrace:
 [1] top-level scope
 [2] eval at .\boot.jl:331 [inlined]
 [3] eval(::Symbol) at .\client.jl:449
 [4] #9 at .\none:0 [inlined]
 [5] iterate(::Base.Generator{UnitRange{Int64},var"#9#10"}) at .\generator.jl:47
 [6] test_() at .\none:9
 [7] top-level scope at .\util.jl:175

确实是变量定义域的问题,查看eval的帮助:

  eval(expr)

  Evaluate an expression in the global scope of the containing module. Every
  Module (except those defined with baremodule) has its own 1-argument
  definition of eval, which evaluates expressions in that module.

需要变量在全局有定义。

1 个赞

好的,完美解决了我的疑惑,感谢感谢

如果vec个数比较多,那就不应该采用这么多个变量名来存储数据,而是直接

result = []
for i = 1:100
    push!(result, get_some_data(i))
end
vcat(result...)

而这种写法基本上都可以mapreduce:

mapreduce(x->get_some_data(x), vcat, 1:100)

大部分情况下,你可以使用宏不代表你应该使用宏

1 个赞

实际上,问题恰好在

get_some_data(i)

部分, 每一组数据的算法都是不同的,这就是为什么我需要写那么多变量的原因. 如果不采用这么多变量, 那我暂时能想到的就是分支选择语句了,那将更麻烦。

我觉得思路应该放在把算法的API统一上,然后通过

algorithm_list = [alg_foo, alg_bar, alg_baz, ...]
X, Y = some inputs
mapreduce(alg->alg(X, Y), vcat, algorithm_list)

这种形式来写… 写条件分支实际上也是在写API wrapper…

不清楚具体的代码结构,见仁见义吧就…

1 个赞

确实是很好的建议, 感谢分享您的思路。