merge dense with sparse now works

Not yet sure I'm right about the semantics of sparse-with-dense: are nils in a dense array first class values?
This commit is contained in:
Simon Brooke 2019-06-28 09:45:05 +01:00
parent 59fb5529aa
commit 1aa3173dc9
3 changed files with 93 additions and 28 deletions

View file

@ -210,7 +210,7 @@ e.g.
### extract-from-dense ### extract-from-dense
Note that the above example returns the default axis sequence {i0, i1, i2...}; Note that the above example returns the default axis sequence `{i0, i1, i2...}`;
extracting from a sparse array will always retain the axes of the array extracting from a sparse array will always retain the axes of the array
extracted from. Dense arrays, obviously, do not have explicit axes. extracted from. Dense arrays, obviously, do not have explicit axes.

View file

@ -42,6 +42,10 @@
(defn sparse-array? (defn sparse-array?
"`true` if `x` is a sparse array conforming to the conventions established "`true` if `x` is a sparse array conforming to the conventions established
by this library, else `false`." by this library, else `false`."
;; TODO: sparse-array? should not throw exceptions even when
;; *safe-sparse-operations* is true, since we may use to test
;; whether an object is a sparse array. The place to throw the exceptions
;; (if required) is after it has failed.
([x] ([x]
(apply (apply
sparse-array? sparse-array?
@ -171,6 +175,31 @@
:else :else
(unsafe-get array coordinates))) (unsafe-get array coordinates)))
(defn dense-dimensions
"How many usable dimensions (represented as vectors) does the dense array
`x` have?"
[x]
(if
(vector? x)
(if
(every? vector? x)
(inc (apply min (map dense-dimensions x)))
;; `min` is right here, not `max`, because otherwise
;; we will get malformed arrays. Be liberal with what you
;; consume, conservative with what you return!
1)
0))
(defn dense-array?
"Basically, any vector can be considered as a dense array of one dimension.
If we're seeking a dense array of more than one dimension, the number of
dimensions should be specified as `d`."
([x]
(vector? x))
([x d]
(and (vector? x) (< d (dense-dimensions x)))))
(defn merge-sparse-arrays (defn merge-sparse-arrays
"Return a sparse array taking values from sparse arrays `a1` and `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` but preferring values from `a2` where there is a conflict. `a1` and `a2`
@ -198,29 +227,55 @@
(keys a1) (keys a1)
(keys a2)))))))) (keys a2))))))))
(defn dense-dimensions (defn merge-dense-with-sparse
"How many usable dimensions (represented as vectors) does the dense array "Merge this dense array `d` with this sparse array `s`, returning a new
`x` have?" dense array with the same arity as `d`, preferring values from `s` where
[x] there is conflict"
(if [d s]
(vector? x) (apply
(if vector
(every? vector? x) (map
(inc (apply min (map dense-dimensions x))) #(cond
;; `min` is right here, not `max`, because otherwise (= :data (:content s))
;; we will get malformed arrays. Be liberal with what you (or (s %2) %1)
;; consume, conservative with what you return! (nil? (s %2))
1) %1
0)) :else
(merge-dense-with-sparse %1 (s %2)))
d
(range))))
(defn dense-array? (defn merge-arrays
"Basically, any vector can be considered as a dense array of one dimension. "Merge two arrays `a1`, `a2`, which may be either dense or sparse but which
If we're seeking a dense array of more than one dimension, the number of should have the same number of axes and compatible dimensions, and return a
dimensions should be specified as `d`." new dense array preferring values from `a2`."
([x] [a1 a2]
(vector? x)) (cond
([x d] (dense-array? a2)
(and (vector? x) (< d (dense-dimensions x))))) a2 ;; if a2 is dense, no values from a1 will be returned
(sparse-array? a1)
(cond
(sparse-array? a2)
(merge-sparse-arrays a1 a2)
*safe-sparse-operations*
(throw
(ex-info
"Object passed as array is neither dense not sparse"
{:array a2})))
(dense-array? a1)
(cond
(sparse-array? a2)
(merge-dense-with-sparse a1 a2)
*safe-sparse-operations*
(throw
(ex-info
"Object passed as array is neither dense not sparse"
{:array a2})))
*safe-sparse-operations*
(throw
(ex-info
"Object passed as array is neither dense not sparse"
{:array a1}))))
(defn dense-to-sparse (defn dense-to-sparse
"Return a sparse array representing the content of the dense array `x`, "Return a sparse array representing the content of the dense array `x`,

View file

@ -172,20 +172,30 @@
(get (make-sparse-array :x :y :z) 3 4 5 6)))))) (get (make-sparse-array :x :y :z) 3 4 5 6))))))
(deftest merge-test (deftest merge-test
(testing "merge, one dimension" (testing "merge, sparse arrays, one dimension"
(let (let
[merged (merge-sparse-arrays [merged (merge-arrays
(put (make-sparse-array :x) "hello" 3) (put (make-sparse-array :x) "hello" 3)
(put (make-sparse-array :x) "goodbye" 4))] (put (make-sparse-array :x) "goodbye" 4))]
(is (= "hello" (get merged 3))) (is (= "hello" (get merged 3)))
(is (= "goodbye" (get merged 4))))) (is (= "goodbye" (get merged 4)))))
(testing "merge, two dimensions" (testing "merge, sparse arrays, two dimensions"
(let (let
[merged (merge-sparse-arrays [merged (merge-arrays
(put (make-sparse-array :x :y) "hello" 3 4) (put (make-sparse-array :x :y) "hello" 3 4)
(put (make-sparse-array :x :y) "goodbye" 4 3))] (put (make-sparse-array :x :y) "goodbye" 4 3))]
(is (= "hello" (get merged 3 4))) (is (= "hello" (get merged 3 4)))
(is (= "goodbye" (get merged 4 3)))))) (is (= "goodbye" (get merged 4 3)))))
(testing "merge, dense with sparse, two dimensions")
(let [dense [[[nil nil nil][nil nil nil][nil nil nil]]
[[nil nil nil][nil nil nil][nil nil nil]]
[[nil nil nil][nil nil nil][nil nil nil]]]
sparse (put (put (make-sparse-array :x :y :z) "hello" 0 0 0) "goodbye" 2 2 2)
expected [[["hello" nil nil] [nil nil nil] [nil nil nil]]
[[nil nil nil] [nil nil nil] [nil nil nil]]
[[nil nil nil] [nil nil nil] [nil nil "goodbye"]]]
actual (merge-arrays dense sparse)]
(is (= actual expected))))
(deftest dense-to-sparse-tests (deftest dense-to-sparse-tests
(testing "dense-to-sparse, one dimension" (testing "dense-to-sparse, one dimension"