lein-release plugin: preparing 0.1.0 release

This commit is contained in:
Simon Brooke 2019-06-26 12:08:30 +01:00
parent b7e6c36d68
commit bc1af9cdb0
4 changed files with 132 additions and 32 deletions

View file

@ -4,6 +4,8 @@ A Clojure library designed to manipulate sparse *arrays* - multi-dimensional spa
Arbitrary numbers of dimensions are supported, up to limits imposed by the JVM stack. Arbitrary numbers of dimensions are supported, up to limits imposed by the JVM stack.
[![Clojars Project](https://img.shields.io/clojars/v/sparse-array.svg)](https://clojars.org/sparse-array)
## Conventions: ## Conventions:
### Sparse arrays ### Sparse arrays
@ -32,7 +34,26 @@ Thus an array with a single value 'hello' at coordinates x = 3, y = 4, z = 5 wou
} }
``` ```
At the present stage of development, where the expectations of an operation are violated, `nil` is returned and no exception is thrown. However, it's probable that later there will be at least the option of thowing specific exceptions, as otherwise debugging could be tricky. ### 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:
```clojure
(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:
```clojure
(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 make wish to bind it to `true` especially while debugging.
### Dense arrays ### Dense arrays

View file

@ -1,4 +1,4 @@
(defproject sparse-array "0.1.0-SNAPSHOT" (defproject sparse-array "0.1.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/)." :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" :url "http://example.com/FIXME"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,12 +1,14 @@
(ns sparse-array.core) (ns sparse-array.core)
(declare put get)
(def ^:dynamic *safe-sparse-operations* (def ^:dynamic *safe-sparse-operations*
"Whether spase array operations should be conducted safely, with careful "Whether spase array operations should be conducted safely, with careful
checking of data conventions and exceptions thrown if expectations are not checking of data conventions and exceptions thrown if expectations are not
met. Normally `false`." met. Normally `false`."
false) false)
(defmacro unsafe-sparse-operations? (defn- unsafe-sparse-operations?
"returns `true` if `*safe-sparse-operations*` is `false`, and vice versa." "returns `true` if `*safe-sparse-operations*` is `false`, and vice versa."
[] []
(not (true? *safe-sparse-operations*))) (not (true? *safe-sparse-operations*)))
@ -79,13 +81,9 @@
sparse-array? sparse-array?
(map #(x %) (filter integer? (keys x))))))))) (map #(x %) (filter integer? (keys x)))))))))
(defn put (defn- unsafe-put
"Return a sparse array like this `array` but with this `value` at these [array value coordinates]
`coordinates`. Returns `nil` if any coordinate is invalid."
[array value & coordinates]
(cond (cond
(and *safe-sparse-operations* (not (sparse-array? array)))
(throw (ex-info "Sparse array expected" {:array array}))
(every? (every?
#(and (integer? %) (or (zero? %) (pos? %))) #(and (integer? %) (or (zero? %) (pos? %)))
coordinates) coordinates)
@ -101,22 +99,58 @@
(or (or
(array (first coordinates)) (array (first coordinates))
(apply make-sparse-array (:content array))) (apply make-sparse-array (:content array)))
(cons value (rest coordinates)))))) (cons value (rest coordinates))))))))
*safe-sparse-operations*
(defn put
"Return a sparse array like this `array` but with this `value` at these
`coordinates`. Returns `nil` if any coordinate is invalid."
[array value & coordinates]
(cond
(nil? value)
nil
(unsafe-sparse-operations?)
(unsafe-put array value coordinates)
(not (sparse-array? array))
(throw (ex-info "Sparse array expected" {:array array}))
(not= (:dimensions array) (count coordinates))
(throw
(ex-info
(str "Expected " (:dimensions array) " coordinates; found " (count coordinates))
{:array array
:coordinates coordinates}))
(not
(every?
#(and (integer? %) (or (zero? %) (pos? %)))
coordinates))
(throw (throw
(ex-info (ex-info
"Coordinates must be zero or positive integers" "Coordinates must be zero or positive integers"
{:array array {:array array
:coordinates coordinates :coordinates coordinates
:invalid (remove #(and (pos? %) (integer? %)) coordinates)})))) :invalid (remove #(and (pos? %) (integer? %)) coordinates)}))
:else
(unsafe-put array value coordinates)
value
*safe-sparse-operations*))
(defn- unsafe-get
;; TODO: I am CERTAIN there is a more elegant solution to this.
[array coordinates]
(let [v (array (first coordinates))]
(cond
(= :data (:content array))
v
(nil? v)
nil
:else
(apply get (cons v (rest coordinates))))))
(defn get (defn get
"Return the value in this sparse `array` at these `coordinates`." "Return the value in this sparse `array` at these `coordinates`."
;; TODO: I am CERTAIN there is a more elegant solution to this.
[array & coordinates] [array & coordinates]
(cond (cond
*safe-sparse-operations* (unsafe-sparse-operations?)
(cond (unsafe-get array coordinates)
(not (sparse-array? array)) (not (sparse-array? array))
(throw (ex-info "Sparse array expected" {:array array})) (throw (ex-info "Sparse array expected" {:array array}))
(not (every? (not (every?
@ -127,11 +161,15 @@
"Coordinates must be zero or positive integers" "Coordinates must be zero or positive integers"
{:array array {:array array
:coordinates coordinates :coordinates coordinates
:invalid (remove #(and (pos? %) (integer? %)) coordinates)}))) :invalid (remove #(and (pos? %) (integer? %)) coordinates)}))
(= :data (:content array)) (not (= (:dimensions array) (count coordinates)))
(array (first coordinates)) (throw
(ex-info
(str "Expected " (:dimensions array) " coordinates; found " (count coordinates))
{:array array
:coordinates coordinates}))
:else :else
(apply get (cons (array (first coordinates)) (rest coordinates))))) (unsafe-get array coordinates)))
(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`,

View file

@ -101,6 +101,13 @@
:content '(:y) :content '(:y)
3 {:dimensions 1 :coord :y :content :data 4 "hello"}} 3 {:dimensions 1 :coord :y :content :data 4 "hello"}}
actual (get array 3 4)] actual (get array 3 4)]
(is (= actual expected)))
(let [expected nil
array {:dimensions 2,
:coord :x,
:content '(:y)
3 {:dimensions 1 :coord :y :content :data 4 "hello"}}
actual (get array 4 3)]
(is (= actual expected)))) (is (= actual expected))))
(testing "put" (testing "put"
(let [expected "hello" (let [expected "hello"
@ -113,12 +120,24 @@
(let (let
[expected "hello" [expected "hello"
actual (get (put (make-sparse-array :x) expected 3) 3)] actual (get (put (make-sparse-array :x) expected 3) 3)]
(is (= actual expected)))) (is (= actual expected)))
(binding [*safe-sparse-operations* true]
;; enabling error handling shouldn't make any difference
(let
[expected "hello"
actual (get (put (make-sparse-array :x) expected 3) 3)]
(is (= actual expected)))))
(testing "round trip, two dimensions" (testing "round trip, two dimensions"
(let (let
[expected "hello" [expected "hello"
actual (get (put (make-sparse-array :x :y) expected 3 4) 3 4)] actual (get (put (make-sparse-array :x :y) expected 3 4) 3 4)]
(is (= actual expected)))) (is (= actual expected)))
(binding [*safe-sparse-operations* true]
;; enabling error handling shouldn't make any difference
(let
[expected "hello"
actual (get (put (make-sparse-array :x :y) expected 3 4) 3 4)]
(is (= actual expected)))))
(testing "round trip, three dimensions" (testing "round trip, three dimensions"
(let (let
[expected "hello" [expected "hello"
@ -128,7 +147,29 @@
(let (let
[expected "hello" [expected "hello"
actual (get (put (make-sparse-array :p :q :r :s) expected 3 4 5 6) 3 4 5 6)] actual (get (put (make-sparse-array :p :q :r :s) expected 3 4 5 6) 3 4 5 6)]
(is (= actual expected))))) (is (= actual expected))))
(testing "Error handling, number of dimensions"
(binding [*safe-sparse-operations* true]
(is
(thrown-with-msg?
clojure.lang.ExceptionInfo
#"Expected 3 coordinates; found 2"
(put (make-sparse-array :x :y :z) "hello" 3 4)))
(is
(thrown-with-msg?
clojure.lang.ExceptionInfo
#"Expected 3 coordinates; found 4"
(put (make-sparse-array :x :y :z) "hello" 3 4 5 6)))
(is
(thrown-with-msg?
clojure.lang.ExceptionInfo
#"Expected 3 coordinates; found 2"
(get (make-sparse-array :x :y :z) 3 4)))
(is
(thrown-with-msg?
clojure.lang.ExceptionInfo
#"Expected 3 coordinates; found 4"
(get (make-sparse-array :x :y :z) 3 4 5 6))))))
(deftest merge-test (deftest merge-test
(testing "merge, one dimension" (testing "merge, one dimension"