Джулия: Могу ли я обновить и сохранить один и тот же массив в пределах итерации for-l oop? - PullRequest
2 голосов
/ 28 января 2020

Я пытаюсь обновить массив с -l oop и сохранить «текущую» версию массива в пределах той же итерации l oop следующим образом:

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» в l oop, но это также очень неэффективно.

Есть ли лучше сделать это?

Спасибо!

Ответы [ 2 ]

3 голосов
/ 28 января 2020

Массивы, которые вы описываете, с одним индексом, хранящим специальное значение, могут быть эффективно представлены с помощью 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 голосов
/ 28 января 2020

Как вы уже догадались, ваша проблема в том, что массив, который вы вставляете в 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)
...