Merge tag 'sparse-array-0.2.0'

This commit is contained in:
Simon Brooke 2019-06-27 09:54:08 +01:00
commit 9494421087
5 changed files with 197 additions and 7 deletions

View file

@ -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

View file

@ -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"

View file

@ -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

View 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}))))

View 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))))))