I *think* the code is right an the tests are wrong, but I'm too unwell/tired to verify that just now. |
||
|---|---|---|
| doc | ||
| src/sparse_array | ||
| test/sparse_array | ||
| .gitignore | ||
| CHANGELOG.md | ||
| LICENSE | ||
| project.clj | ||
| README.md | ||
sparse-array
A Clojure library designed to manipulate sparse arrays - multi-dimensional spaces accessed by indices, but containing arbitrary values rather than just numbers. For sparse spaces which contain numbers only, you're better to use a sparse matrix library, for example clojure.core.matrix.
Arbitrary numbers of dimensions are supported, up to limits imposed by the JVM stack.
Conventions:
Sparse arrays
For the purposes of this library, a sparse array shall be implemented as a map, such that all keys are non-negative members of the set of integers, except for the following keyword keys, all of which are expected to be present:
:dimensionsThe number of dimensions in this array, counting the present one (value expected to be a real number);:coordThe coordinate of the dimension represented by the current map (value expected to be a keyword);:contentWhat this map contains; if the value of:dimensionsis one, then:data; otherwise, an ordered sequence of the coordinates of the dimensions below this one.
Thus an array with a single value 'hello' at coordinates x = 3, y = 4, z = 5 would be encoded:
{:dimensions 3
:coord :x
:content [:y :z]
3 {:dimensions 2
:coord :y
:content [:z]
4 {:dimensions 1
:coord :z
:content :data
5 "hello"
}
}
}
Errors and error-reporting
A dynamic variable, *safe-sparse-operations*, is provided to handle behaviour in error conditions. If this is false, bad data will generally not cause an exception to be thrown, and corrupt structures may be returned, thus:
(put (make-sparse-array :x :y :z) "hello" 3) ;; insufficient coordinates specified
=> {:dimensions 3, :coord :x, :content (:y :z), 3 {:dimensions 2, :coord :y, :content (:z), nil {:dimensions 1, :coord :z, :content :data, nil nil}}}
However, if *safe-sparse-operations* is bound to true, exceptions will be thrown instead:
(binding [*safe-sparse-operations* true]
(put (make-sparse-array :x :y :z) "hello" 3))
ExceptionInfo Expected 3 coordinates; found 1 clojure.core/ex-info (core.clj:4617)
Sanity checking data is potentially expensive; for this reason *safe-sparse-operations* defaults to false, but you may wish to bind it to true especially while debugging.
Dense arrays
For the purposes of conversion, a dense array is assumed to be a vector; a two dimensional dense array a vector of vectors; a three dimensional dense array a vector of vectors of vectors, and so on. For any depth N, all vectors at depth N must have the same arity. If these conventions are not respected conversion may fail.
Usage
make-sparse-array
sparse-array.core/make-sparse-array ([& dimensions])
Make a sparse array with these dimensions. Every member of dimensions must be a keyword; otherwise, nil will be returned.
e.g.
(make-sparse-array :x :y :z)
=> {:dimensions 3, :coord :x, :content (:y :z)}
sparse-array?
sparse-array.core/sparse-array? ([x])
true if x is a sparse array conforming to the conventions established by this library, else false.
put
sparse-array.core/put ([array value & coordinates])
Return a sparse array like this array but with this value at these coordinates. Returns nil if any coordinate is invalid.
e.g.
(put (put (make-sparse-array :x :y) "hello" 3 4) "goodbye" 4 3)
=> {:dimensions 2,
:coord :x,
:content (:y),
3 {:dimensions 1, :coord :y, :content :data, 4 "hello"},
4 {:dimensions 1, :coord :y, :content :data, 3 "goodbye"}}
get
sparse-array.core/get ([array & coordinates])
Return the value in this sparse array at these coordinates.
merge-sparse-arrays
sparse-array.core/merge-sparse-arrays ([a1 a2])
Return a sparse array taking values from sparse arrays a1 and a2, but preferring values from a2 where there is a conflict. a1 and a2 must have the same dimensions in the same order, or nil will be returned.
e.g.
(merge-sparse-arrays
(put (make-sparse-array :x) "hello" 3)
(put (make-sparse-array :x) "goodbye" 4)))
=> {:dimensions 1, :coord :x, :content :data, 3 "hello", 4 "goodbye"}
dense-to-sparse
sparse-array.core/dense-to-sparse ([x] [x coordinates])
Return a sparse array representing the content of the dense array x, assuming these coordinates if specified. NOTE THAT if insufficient values of coordinates are specified, the resulting sparse array will be malformed.
e.g.
(dense-to-sparse [nil nil nil "hello" nil "goodbye"])
=> {:dimensions 1, :coord :i0, :content :data, 3 "hello", 5 "goodbye"}
sparse-to-dense
sparse-array.core/sparse-to-dense ([x] [x arity])
Return a dense array representing the content of the sparse array x.
NOTE THAT this has the potential to consume very large amounts of memory.
e.g.
(sparse-to-dense
(put
(put
(make-sparse-array :x :y)
"hello" 3 4)
"goodbye" 4 3))
=> [[nil nil nil nil nil]
[nil nil nil nil nil]
[nil nil nil nil nil]
[nil nil nil nil "hello"]
[nil nil nil "goodbye" nil]]
License
Copyright © 2019 Simon Brooke
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.