Missing `deleteat!` method for SparseVector

Hey,

I came across the following missing method:

deleteat!(::SparseVector{Float64, Int64}, ::Int64)

Indeed,

julia> x = sparse([0,1,0])
3-element SparseVector{Int64, Int64} with 1 stored entry:
  [2]  =  1

julia> deleteat!(x,1)
ERROR: MethodError: no method matching deleteat!(::SparseVector{Int64, Int64}, ::Int64)
Closest candidates are:
  deleteat!(::Vector{T} where T, ::Integer) at array.jl:1343
  deleteat!(::Vector{T} where T, ::Any) at array.jl:1380
  deleteat!(::BitVector, ::Integer) at bitarray.jl:949
  ...
Stacktrace:
 [1] top-level scope
   @ REPL[53]:1

julia> deleteat!(x,2)
ERROR: MethodError: no method matching deleteat!(::SparseVector{Int64, Int64}, ::Int64)
Closest candidates are:
  deleteat!(::Vector{T} where T, ::Integer) at array.jl:1343
  deleteat!(::Vector{T} where T, ::Any) at array.jl:1380
  deleteat!(::BitVector, ::Integer) at bitarray.jl:949
  ...
Stacktrace:
 [1] top-level scope
   @ REPL[54]:1

julia> x = Vector(x)
3-element Vector{Int64}:
 0
 1
 0

julia> deleteat!(x,2)
2-element Vector{Int64}:
 0
 0

julia> 

To fix it, I added to my script the follwoing:

function Base.deleteat!(x::SparseVector{T,I},idx) where {T,I}
    x = vcat(x[begin:(idx-1)],x[(idx+1):end])
end

But iā€™m sure its suboptimal. What should i do ?

Maybe this?

julia> function deleteat!(x::SparseVector,ind) 
           ind_sparse = findfirst(isequal(ind),x.nzind)
           deleteat!(x.nzval,ind_sparse)
           deleteat!(x.nzind,ind_sparse)
           return x
       end
deleteat! (generic function with 9 methods)

julia> x = sparse([0,1,0,1,0])
5-element SparseVector{Int64, Int64} with 2 stored entries:
  [2]  =  1
  [4]  =  1

julia> deleteat!(x,4)
5-element SparseVector{Int64, Int64} with 1 stored entry:
  [2]  =  1

(maybe searchsortedfirst should be used instead of findfirst there)

Well, as your example shows, and I do not get why, your array still has length 5 which is not correct. You basically just reproduced :

x[ind] .= zero(eltype(x))
1 Like

Ah, then this should do the trick:

julia> using Setfield

julia> function deleteat!(x::SparseVector,ind) 
           ind_sparse = findfirst(isequal(ind),x.nzind)
           deleteat!(x.nzval,ind_sparse)
           deleteat!(x.nzind,ind_sparse)
           @set! x.n -= 1
           return x
       end
deleteat! (generic function with 9 methods)

julia> x = sparse([0,1,0,1,0])
5-element SparseVector{Int64, Int64} with 2 stored entries:
  [2]  =  1
  [4]  =  1

julia> x = deleteat!(x,4)
4-element SparseVector{Int64, Int64} with 1 stored entry:
  [2]  =  1

But be careful that this does not really mutates the original array (because the length is immutable). You have to do x = deletat!(x).

No, that is not the same, because the lengths of the supporting arrays (nzind and nzval) are changed in my example.

Indeed it was not the same.

Thanks for the Setfield showcase, I did not know about this package.

It is actually not needed there. It is the same as doing this (which highlights the fact that you have to be careful with the fact that a new array was defined):

julia> import Base:deleteat!

julia> using SparseArrays

julia> function deleteat!(x::SparseVector,ind) 
           ind_sparse = findfirst(isequal(ind),x.nzind)
           deleteat!(x.nzval,ind_sparse)
           deleteat!(x.nzind,ind_sparse)
           return SparseVector(x.n-1,x.nzind,x.nzval)
       end
deleteat! (generic function with 9 methods)

julia> x = sparse([0,1,0,1,0])
5-element SparseVector{Int64, Int64} with 2 stored entries:
  [2]  =  1
  [4]  =  1

julia> x = deleteat!(x,4)
4-element SparseVector{Int64, Int64} with 1 stored entry:
  [2]  =  1


To obtain the same interface, can i do instead :

function deleteat(x::SparseVector,ind) 
    ind_sparse = findfirst(isequal(ind),x.nzind)
    deleteat!(x.nzval,ind_sparse)
    deleteat!(x.nzind,ind_sparse)
    return SparseVector(x.n-1,x.nzind,x.nzval)
end
function deleteat!(x::Sparsevector,ind)
    x = deleteat(x,ind)
end

So that whatever the vector, calling deleteat!() has the same behavior ? Of course, this masks the fact that the vector has to be redefined.

No, that will be the same. You cannot really have the same interface, because the SparseVector is immutable (it contains mutable fields, but the n field is not mutable). You have to return x and reassign it on return, in any case. (that probably explains why there is no deleteat! defined by default for sparse vectors).

1 Like