Skip to content

Commit 56c17dd

Browse files
committed
Initial commit
1 parent a5816d4 commit 56c17dd

File tree

4 files changed

+409
-0
lines changed

4 files changed

+409
-0
lines changed

.claude/settings.local.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(julia:*)"
5+
],
6+
"deny": [],
7+
"ask": []
8+
}
9+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
module MultidimensionalSparseArrays
22

3+
include("sparse_array.jl")
4+
5+
export SparseArray, nnz, sparsity, stored_indices, stored_values, stored_pairs
6+
37
end

src/sparse_array.jl

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""
2+
SparseArray{T, N} <: AbstractArray{T, N}
3+
4+
A multidimensional sparse array that stores only non-zero elements efficiently.
5+
6+
# Fields
7+
8+
- `data::Dict{CartesianIndex{N}, T}`: Dictionary mapping indices to non-zero values
9+
- `dims::NTuple{N, Int}`: Dimensions of the array
10+
- `default_value::T`: Default value for unset indices (typically zero)
11+
12+
# Examples
13+
14+
```julia
15+
# Create a 3x3 sparse matrix
16+
A = SparseArray{Float64, 2}((3, 3))
17+
A[1, 1] = 5.0
18+
A[2, 3] = 3.0
19+
20+
# Create from existing data
21+
B = SparseArray([1 0 3; 0 0 0; 2 0 0])
22+
```
23+
"""
24+
struct SparseArray{T, N} <: AbstractArray{T, N}
25+
data::Dict{CartesianIndex{N}, T}
26+
dims::NTuple{N, Int}
27+
default_value::T
28+
29+
function SparseArray{T, N}(dims::NTuple{N, Int}, default_value::T = zero(T)) where {T, N}
30+
return new{T, N}(Dict{CartesianIndex{N}, T}(), dims, default_value)
31+
end
32+
end
33+
34+
# Convenience constructors
35+
SparseArray{T}(dims::NTuple{N, Int}, default_value::T = zero(T)) where {T, N} =
36+
SparseArray{T, N}(dims, default_value)
37+
38+
SparseArray{T}(dims::Vararg{Int, N}) where {T, N} =
39+
SparseArray{T, N}(dims, zero(T))
40+
41+
# Constructor from dense array
42+
function SparseArray(A::AbstractArray{T, N}) where {T, N}
43+
sparse_array = SparseArray{T, N}(size(A))
44+
for I in CartesianIndices(A)
45+
val = A[I]
46+
if val != sparse_array.default_value
47+
sparse_array.data[I] = val
48+
end
49+
end
50+
return sparse_array
51+
end
52+
53+
# Required AbstractArray interface
54+
Base.size(A::SparseArray) = A.dims
55+
Base.IndexStyle(::Type{<:SparseArray}) = IndexCartesian()
56+
57+
# Indexing
58+
@inline function Base.getindex(A::SparseArray{T, N}, I::Vararg{Int, N}) where {T, N}
59+
@boundscheck checkbounds(A, I...)
60+
idx = CartesianIndex(I)
61+
return get(A.data, idx, A.default_value)
62+
end
63+
64+
@inline function Base.getindex(A::SparseArray, I::CartesianIndex)
65+
@boundscheck checkbounds(A, I)
66+
return get(A.data, I, A.default_value)
67+
end
68+
69+
@inline function Base.setindex!(A::SparseArray{T, N}, val, I::Vararg{Int, N}) where {T, N}
70+
@boundscheck checkbounds(A, I...)
71+
idx = CartesianIndex(I)
72+
if val == A.default_value
73+
delete!(A.data, idx)
74+
else
75+
A.data[idx] = val
76+
end
77+
return val
78+
end
79+
80+
@inline function Base.setindex!(A::SparseArray, val, I::CartesianIndex)
81+
@boundscheck checkbounds(A, I)
82+
if val == A.default_value
83+
delete!(A.data, I)
84+
else
85+
A.data[I] = val
86+
end
87+
return val
88+
end
89+
90+
# Iteration
91+
function Base.iterate(A::SparseArray)
92+
iter_state = iterate(CartesianIndices(A))
93+
isnothing(iter_state) && return nothing
94+
idx, state = iter_state
95+
return (A[idx], state)
96+
end
97+
98+
function Base.iterate(A::SparseArray, state)
99+
iter_state = iterate(CartesianIndices(A), state)
100+
isnothing(iter_state) && return nothing
101+
idx, new_state = iter_state
102+
return (A[idx], new_state)
103+
end
104+
105+
# Additional useful methods
106+
"""
107+
nnz(A::SparseArray)
108+
109+
Return the number of stored (non-zero) elements in the sparse array.
110+
"""
111+
nnz(A::SparseArray) = length(A.data)
112+
113+
"""
114+
sparsity(A::SparseArray)
115+
116+
Return the sparsity ratio (fraction of zero elements) of the array.
117+
"""
118+
sparsity(A::SparseArray) = 1.0 - nnz(A) / length(A)
119+
120+
"""
121+
stored_indices(A::SparseArray)
122+
123+
Return an iterator over the indices that have stored values.
124+
"""
125+
stored_indices(A::SparseArray) = keys(A.data)
126+
127+
"""
128+
stored_values(A::SparseArray)
129+
130+
Return an iterator over the stored non-zero values.
131+
"""
132+
stored_values(A::SparseArray) = values(A.data)
133+
134+
"""
135+
stored_pairs(A::SparseArray)
136+
137+
Return an iterator over (index, value) pairs for stored elements.
138+
"""
139+
stored_pairs(A::SparseArray) = pairs(A.data)
140+
141+
# Display
142+
function Base.show(io::IO, ::MIME"text/plain", A::SparseArray{T, N}) where {T, N}
143+
println(io, "$(size(A)) SparseArray{$T, $N} with $(nnz(A)) stored entries:")
144+
if nnz(A) > 0
145+
for (idx, val) in stored_pairs(A)
146+
println(io, " $idx => $val")
147+
end
148+
end
149+
end
150+
151+
# Basic arithmetic operations
152+
Base.:(==)(A::SparseArray, B::SparseArray) =
153+
size(A) == size(B) && A.default_value == B.default_value && A.data == B.data
154+
155+
# Copy
156+
function Base.copy(A::SparseArray{T, N}) where {T, N}
157+
B = SparseArray{T, N}(A.dims, A.default_value)
158+
for (k, v) in A.data
159+
B.data[k] = v
160+
end
161+
return B
162+
end
163+
164+
Base.similar(A::SparseArray{T, N}) where {T, N} =
165+
SparseArray{T, N}(A.dims, A.default_value)
166+
167+
Base.similar(A::SparseArray{T, N}, ::Type{S}) where {T, S, N} =
168+
SparseArray{S, N}(A.dims, zero(S))

0 commit comments

Comments
 (0)