Arrays 朱莉娅:我可以在for循环的迭代中更新和存储相同的数组吗?

Arrays 朱莉娅:我可以在for循环的迭代中更新和存储相同的数组吗?,arrays,for-loop,matrix,indexing,julia,Arrays,For Loop,Matrix,Indexing,Julia,我尝试使用for循环更新数组,并在循环的同一迭代中存储数组的“当前”版本,如下所示: struct store a::Float64 mat::AbstractArray end function foo(x::AbstractArray) m, n = size(x) col = Array{store}(undef, m, n) A = zeros(m, n) for i in eachindex(col) A[i] =

我尝试使用for循环更新数组,并在循环的同一迭代中存储数组的“当前”版本,如下所示:

struct store
    a::Float64
    mat::AbstractArray
end

function foo(x::AbstractArray)
    m, n  = size(x)
    col = Array{store}(undef, m, n)
    A = zeros(m, n)

    for i in eachindex(col)
        A[i] = 1.0
        print(A)
        col[i] = store(x[i], A)
        A[i] = 0
    end

    return col
end
我添加了一个print()来检查数组是否按我希望的方式更新(它是这样的)。我要存储的矩阵除了索引当前位置的“1”之外,其他都是零。我得到的结果是:

foo(rand(2,2))

2×2 Array{store,2}:
store(0.447322, [0.0 0.0; 0.0 0.0])  store(0.949405, [0.0 0.0; 0.0 0.0])
store(0.56251, [0.0 0.0; 0.0 0.0])   store(0.156834, [0.0 0.0; 0.0 0.0])
通过在循环中放置数组“A”可以实现我想要的,但这也是非常低效的

有更好的办法吗


谢谢

使用
SparseArrays
可以有效地表示您描述的数组,其中单个索引存储一个特殊值

因为它们遵循一致的模式,所以可以动态生成矩阵。您可以避免一次将它们全部存储在内存中

这是一个节省内存的解决方案:

julia> using SparseArrays

julia> struct Store{N}
           A::Array{Float64,N}
       end

julia> function Base.getindex(store::Store, I...)
           B = spzeros(size(store.A)...)
           B[I...] = 1.0
           return store.A[I...], B
       end
您可以这样使用:

julia> foo = rand(2,2)
2×2 Array{Float64,2}:
 0.741406  0.0833667
 0.688376  0.706395

julia> store = Store(foo)
Store{2}([0.7414058497508282 0.08336674477744199; 0.6883759175546191 0.706394665153228])

julia> store[1]
(0.7414058497508282, 
  [1, 1]  =  1.0)
如果您以前没有使用过SparSearray,则SparSearray的打印可能看起来很奇怪,但您可以确认它们的行为符合预期:

julia> a, b = store[4]
(0.706394665153228, 
  [2, 2]  =  1.0)

julia> b[1], b[2], b[3], b[4] # only the fourth index will have a nonzero value
(0.0, 0.0, 0.0, 1.0)

正如您可能已经猜到的,您的问题是由于您插入到
col
中的数组总是相同的:在一次迭代中修改数组会在任何地方修改它

执行所需操作的第一种方法是在将数组插入
col
时复制该数组:

function foo1(x::AbstractArray)
    m, n  = size(x)
    col = Array{store}(undef, m, n)
    A = zeros(m, n)

    for i in eachindex(col)
        A[i] = 1.0
        col[i] = store(x[i], copy(A))
        A[i] = 0
    end

    return col
end

另一种方法是,正如您所建议的,在每次迭代时创建一个新的
a
数组:

function foo2(x::AbstractArray)
    m, n  = size(x)
    col = Array{store}(undef, m, n)

    for i in eachindex(col)
        A = zeros(m, n)
        A[i] = 1.0
        col[i] = store(x[i], A)
    end

    return col
end

看起来第二种方法更有效:

julia> using BenchmarkTools

julia> x = rand(2,2)
2×2 Array{Float64,2}:
 0.899445  0.459424
 0.287892  0.669846

julia> @btime foo1($x)
  241.078 ns (10 allocations: 800 bytes)
2×2 Array{store,2}:
 store(0.899445, [1.0 0.0; 0.0 0.0])  store(0.459424, [0.0 1.0; 0.0 0.0])
 store(0.287892, [0.0 0.0; 1.0 0.0])  store(0.669846, [0.0 0.0; 0.0 1.0])

julia> @btime foo2($x)
  198.404 ns (9 allocations: 688 bytes)
2×2 Array{store,2}:
 store(0.899445, [1.0 0.0; 0.0 0.0])  store(0.459424, [0.0 1.0; 0.0 0.0])
 store(0.287892, [0.0 0.0; 1.0 0.0])  store(0.669846, [0.0 0.0; 0.0 1.0])

正如在另一个答案中所说,将
A
存储为
SparseArray
会更有效,尤其是当它的大小较大时:

using SparseArrays
function foo3(x::AbstractArray)
    m, n  = size(x)
    col = Array{store}(undef, m, n)

    for i in eachindex(col)
        A = spzeros(m, n)
        A[i] = 1.0
        col[i] = store(x[i], A)
    end

    return col
end
这种策略对如此小的尺寸没有好处,但如果您的实际问题更大,它应该是最有效的:

julia> @btime foo3($x)
  829.851 ns (33 allocations: 1.92 KiB)
2×2 Array{store,2}:
 store(0.899445, [1, 1]  =  1.0)  store(0.459424, [1, 2]  =  1.0)
 store(0.287892, [2, 1]  =  1.0)  store(0.669846, [2, 2]  =  1.0)

如果您关心效率,那么应该避免在结构定义中使用抽象字段类型。如果要允许多种类型的数组,请使用参数化结构。(还有一条建议:对类型名称使用大写,而不是小写。)