It is trivial to implement something like this (note: I didn’t optimize or test this code thoroughly, just a proof of concept), eg
mutable struct ExpandingArray{T,N} <: AbstractArray{T,N}
contents::Array{T,N}
filler::T
function ExpandingArray{N}(filler::T) where {T,N}
@assert N ≥ 1
contents = Array{T}(undef, ntuple(_ -> 0, Val(N)))
new{T,N}(contents, filler)
end
end
Base.size(a::ExpandingArray) = size(a.contents)
Base.getindex(a::ExpandingArray, i...) = a.contents[i...]
function Base.setindex!(a::ExpandingArray{T,N}, x, i...) where {T,N}
@assert length(i) == N
D = size(a.contents)
if any(i .> D)
D′ = max.(D, i)
c′ = fill(a.filler, D′)
c′[axes(a.contents)...] .= a.contents
a.contents = c′
end
a.contents[i...] = x
end
Eg
julia> e = ExpandingArray{2}(0.0)
0×0 ExpandingArray{Float64, 2}
julia> e[1,3] = 3.0
3.0
julia> e[4,2] = 5.0
5.0
julia> e
4×3 ExpandingArray{Float64, 2}:
0.0 0.0 3.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 5.0 0.0
that can tide you over while you are porting your code, but as others have remarked, it is much more efficient to preallocate the correct size.