From 551fd00292ea9f0f10fe9d40e7c59c4b102ac420 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 26 Jun 2019 18:38:18 +0100 Subject: [PATCH] Extract tests are failing because the indexes aren't as expected I *think* the code is right an the tests are wrong, but I'm too unwell/tired to verify that just now. --- README.md | 2 +- src/sparse_array/core.clj | 19 ++++-- src/sparse_array/extract.clj | 68 +++++++++++++++++++++ test/sparse_array/extract_test.clj | 94 ++++++++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 src/sparse_array/extract.clj create mode 100644 test/sparse_array/extract_test.clj diff --git a/README.md b/README.md index a6d6be5..6e9f859 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/sparse_array/core.clj b/src/sparse_array/core.clj index c984580..1b796e6 100644 --- a/src/sparse_array/core.clj +++ b/src/sparse_array/core.clj @@ -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 diff --git a/src/sparse_array/extract.clj b/src/sparse_array/extract.clj new file mode 100644 index 0000000..7cb36fb --- /dev/null +++ b/src/sparse_array/extract.clj @@ -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})))) + diff --git a/test/sparse_array/extract_test.clj b/test/sparse_array/extract_test.clj new file mode 100644 index 0000000..01fc3ca --- /dev/null +++ b/test/sparse_array/extract_test.clj @@ -0,0 +1,94 @@ +(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 1 + 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 strings 0 0 2)] + (is (= actual expected))) + (let [expected "three" + actual (get threes 0 0 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 1 + 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 strings 0 0 2)] + (is (= actual expected))) + (let [expected "three" + actual (get threes 0 0 2)] + (is (= actual expected))))))