Merge tag 'sparse-array-0.2.0'
This commit is contained in:
commit
9494421087
|
@ -53,7 +53,7 @@ However, if `*safe-sparse-operations*` is bound to `true`, exceptions will be th
|
|||
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 make wish to bind it to `true` especially while debugging.
|
||||
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
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(defproject sparse-array "0.1.0"
|
||||
(defproject sparse-array "0.2.0"
|
||||
:description "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](https://mikera.github.io/core.matrix/)."
|
||||
:url "http://example.com/FIXME"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
@ -213,25 +213,34 @@
|
|||
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 dense-to-sparse
|
||||
"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
|
||||
assuming these `axes` if specified. *NOTE THAT* if insufficient
|
||||
values of `axes` are specified, the resulting sparse array will
|
||||
be malformed."
|
||||
([x]
|
||||
(dense-to-sparse x (map #(keyword (str "i" %)) (range))))
|
||||
([x coordinates]
|
||||
([x axes]
|
||||
(let
|
||||
[dimensions (dense-dimensions x)]
|
||||
(reduce
|
||||
merge
|
||||
(apply make-sparse-array (take dimensions coordinates))
|
||||
(apply make-sparse-array (take dimensions axes))
|
||||
(map
|
||||
(fn [i v] (if (nil? v) nil (hash-map i v)))
|
||||
(range)
|
||||
(if
|
||||
(> dimensions 1)
|
||||
(map #(dense-to-sparse % (rest coordinates)) x)
|
||||
(map #(dense-to-sparse % (rest axes)) x)
|
||||
x))))))
|
||||
|
||||
(defn arity
|
||||
|
|
68
src/sparse_array/extract.clj
Normal file
68
src/sparse_array/extract.clj
Normal file
|
@ -0,0 +1,68 @@
|
|||
(ns sparse-array.extract
|
||||
(:require [sparse-array.core :refer :all]))
|
||||
|
||||
;;; The whole point of working with sparse arrays is to work with interesting
|
||||
;;; subsets of arrays most of which are uninteresting. To extract an
|
||||
;;; interesting subset from an array, we're going to need an extract function.
|
||||
|
||||
(defn- extract-from-sparse
|
||||
"Return a subset of this sparse `array` comprising all those cells for which
|
||||
this `function` returns a 'truthy' value."
|
||||
[array function]
|
||||
(reduce
|
||||
merge
|
||||
(apply
|
||||
make-sparse-array
|
||||
(cons
|
||||
(:coord array)
|
||||
(if (coll? (:content array)) (:content array))))
|
||||
(map
|
||||
#(if
|
||||
(= :data (:content array))
|
||||
(if (function (array %)) {% (array %)})
|
||||
(let [v (extract-from-sparse (array %) function)]
|
||||
(if
|
||||
(empty?
|
||||
(filter integer? (keys v)))
|
||||
nil
|
||||
{% v})))
|
||||
(filter integer? (keys array)))))
|
||||
|
||||
(defn extract-from-dense
|
||||
"Return a subset of this dense `array` comprising all those cells for which
|
||||
this `function` returns a 'truthy' value. Use these `axes` if provided."
|
||||
([array function]
|
||||
(extract-from-dense array function (map #(keyword (str "i" %)) (range))))
|
||||
([array function axes]
|
||||
(let
|
||||
[dimensions (dense-dimensions array)]
|
||||
(reduce
|
||||
merge
|
||||
(apply make-sparse-array (take dimensions axes))
|
||||
(if
|
||||
(= dimensions 1)
|
||||
(map
|
||||
(fn [i v] (if (function v) (hash-map i v)))
|
||||
(range)
|
||||
array)
|
||||
(map
|
||||
(fn [i v] (if (empty? (filter integer? (keys v))) nil (hash-map i v)))
|
||||
(range)
|
||||
(map #(extract-from-dense % function (rest axes)) array)))))))
|
||||
|
||||
(defn extract
|
||||
"Return a sparse subset of this `array` - which may be either sparse or
|
||||
dense - comprising all those cells for which this `function` returns a
|
||||
'truthy' value."
|
||||
[array function]
|
||||
(cond
|
||||
(sparse-array? array)
|
||||
(extract-from-sparse array function)
|
||||
(dense-array? array)
|
||||
(extract-from-dense array function)
|
||||
*safe-sparse-operations*
|
||||
(throw
|
||||
(ex-info
|
||||
"Argument passed as `array` is neither sparse nor dense."
|
||||
{:array array}))))
|
||||
|
113
test/sparse_array/extract_test.clj
Normal file
113
test/sparse_array/extract_test.clj
Normal file
|
@ -0,0 +1,113 @@
|
|||
(ns sparse-array.extract-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[sparse-array.core :refer [dense-to-sparse get]]
|
||||
[sparse-array.extract :refer :all]))
|
||||
|
||||
|
||||
(deftest sparse-tests
|
||||
(testing "extraction from sparse array"
|
||||
(let [dense [[[1 2 3][:one :two :three]["one" "two" "three"]]
|
||||
[[1 :two "three"]["one" 2 :three][:one "two" 3]]
|
||||
[[1.0 2.0 3.0][2/2 4/2 6/2]["I" "II" "III"]]]
|
||||
sparse (dense-to-sparse dense '(:a :b :c))
|
||||
integers (extract sparse integer?)
|
||||
strings (extract sparse string?)
|
||||
keywords (extract sparse keyword?)
|
||||
threes (extract sparse #(if
|
||||
(number? %)
|
||||
(= % 3)
|
||||
(= (name %) "three")))]
|
||||
(map
|
||||
#(let [expected nil
|
||||
actual (get (extract sparse map?) %1 %2 %3)]
|
||||
(is (= actual expected) "there are no cells of which `map?` is true"))
|
||||
(range 3)
|
||||
(range 3)
|
||||
(range 3))
|
||||
(let [expected 1
|
||||
actual (get integers 0 0 0)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get keywords 0 0 0)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get strings 0 0 0)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get threes 0 0 0)]
|
||||
(is (= actual expected)))
|
||||
(let [expected 3
|
||||
actual (get integers 0 0 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get keywords 0 0 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected :three
|
||||
actual (get keywords 0 1 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get strings 0 0 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected 3
|
||||
actual (get threes 0 0 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected :three
|
||||
actual (get threes 0 1 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected "three"
|
||||
actual (get threes 0 2 2)]
|
||||
(is (= actual expected)))
|
||||
)))
|
||||
|
||||
(deftest dense-tests
|
||||
(testing "extraction from dense array"
|
||||
(let [dense [[[1 2 3][:one :two :three]["one" "two" "three"]]
|
||||
[[1 :two "three"]["one" 2 :three][:one "two" 3]]
|
||||
[[1.0 2.0 3.0][2/2 4/2 6/2]["I" "II" "III"]]]
|
||||
integers (extract dense integer?)
|
||||
strings (extract dense string?)
|
||||
keywords (extract dense keyword?)
|
||||
threes (extract dense #(if
|
||||
(number? %)
|
||||
(= % 3)
|
||||
(= (name %) "three")))]
|
||||
(map
|
||||
#(let [expected nil
|
||||
actual (get (extract dense map?) %1 %2 %3)]
|
||||
(is (= actual expected) "there are no cells of which `map?` is true"))
|
||||
(range 3)
|
||||
(range 3)
|
||||
(range 3))
|
||||
(let [expected 1
|
||||
actual (get integers 0 0 0)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get keywords 0 0 0)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get strings 0 0 0)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get threes 0 0 0)]
|
||||
(is (= actual expected)))
|
||||
(let [expected 3
|
||||
actual (get integers 0 0 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get keywords 0 0 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected :three
|
||||
actual (get keywords 0 1 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected nil
|
||||
actual (get strings 0 0 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected 3
|
||||
actual (get threes 0 0 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected :three
|
||||
actual (get threes 0 1 2)]
|
||||
(is (= actual expected)))
|
||||
(let [expected "three"
|
||||
actual (get threes 0 2 2)]
|
||||
(is (= actual expected))))))
|
Loading…
Reference in a new issue