You can use this abomination:
using SparseArrays
struct SparseMatrixBuilder{T}
a::Int
b::Int
is::Vector{Int}
js::Vector{Int}
vs::Vector{T}
SparseMatrixBuilder{T}(a, b) where T = new{T}(a, b, Int[], Int[], T[])
end
struct SparseMatrixBuilderScalarIndexView{T}
builder::SparseMatrixBuilder{T}
i::Int
j::Int
end
function Base.getindex(builder::SparseMatrixBuilder, i::Int, j::Int)
return SparseMatrixBuilderScalarIndexView(builder, i, j)
end
struct SparseMatrixBuilderNewAddition{T}
builder_view::SparseMatrixBuilderScalarIndexView{T}
v::T
end
function Base.:(+)(builder_view::SparseMatrixBuilderScalarIndexView{T}, v::T) where T
return SparseMatrixBuilderNewAddition(builder_view, v)
end
function Base.:(-)(builder_view::SparseMatrixBuilderScalarIndexView{T}, v::T) where T
return SparseMatrixBuilderNewAddition(builder_view, -v)
end
function Base.setindex!(builder::SparseMatrixBuilder{T}, v::SparseMatrixBuilderNewAddition{T}, i::Int, j::Int) where T
if !(i == v.builder_view.i && j == v.builder_view.j && builder === v.builder_view.builder)
error("good error message")
end
if !(1 <= i <= builder.a && 1 <= j <= builder.b)
throw(BoundsError(builder, (i, j)))
end
push!(builder.is, i)
push!(builder.js, j)
push!(builder.vs, v.v)
return v
end
to_matrix(A::SparseMatrixBuilder) = sparse(A.is, A.js, A.vs, A.a, A.b)
julia> builder = SparseMatrixBuilder{Float64}(2, 2);
julia> builder[1, 1] += 1.2;
julia> builder[2, 2] += 3.4;
julia> builder[1, 2] -= 5.6;
julia> to_matrix(builder)
2×2 SparseMatrixCSC{Float64, Int64} with 3 stored entries:
1.2 -5.6
⋅ 3.4
Note that builder[2, 2] works but return a “useless” object – there are no methods for it so will just give MethodError as soon as you try to use it. builder[2, 2] = 3 is a MethodError for example.