【问题标题】:Julia: Can I update and store the same array within an iteration of a for-loop?Julia:我可以在 for 循环的迭代中更新和存储相同的数组吗?
【发布时间】:2020-05-13 13:16:29
【问题描述】:

我正在尝试使用 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”放在循环中来达到我想要的效果,但是效率也很低。

有更好的方法吗?

谢谢!

【问题讨论】:

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

标签: arrays for-loop matrix indexing julia


【解决方案1】:

您描述的数组,具有存储特殊值的单个索引,可以使用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)

如果您以前没有使用过 SparseArrays 的打印可能看起来很奇怪,但您可以确认它们的行为符合预期:

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)

【讨论】:

    【解决方案2】:

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

    第一种方法是在将数组插入col 时将copy 数组插入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)
    

    【讨论】:

      猜你喜欢
      • 2019-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多