Last active
November 30, 2022 12:37
-
-
Save mbezjak/baa6622a6edfa40e61aeff27041266dc to your computer and use it in GitHub Desktop.
clojure.core extensions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.coll | |
"Missing functions in `clojure.core` that accept a collection. | |
Argument names and expected positions use the same convention as `clojure.core.`" | |
(:refer-clojure :exclude [any?])) | |
(defn any? | |
"Opposite of `clojure.core/not-any?`. | |
Returns `false` for empty `coll`." | |
[pred coll] | |
(boolean (some pred coll))) | |
(defn includes? | |
"Does `coll` include `x`? | |
Guaranteed to return a boolean. Time complexity linear, so don't use for large | |
collections." | |
[x coll] | |
(any? #(= x %) coll)) | |
(defn elem | |
"Find the first element matching `pred` in `coll`. | |
Returns `nil` on no match. Time complexity linear, so don't use for large | |
collections." | |
[pred coll] | |
(some #(when (pred %) %) coll)) | |
(defn groups | |
"Group elements in `coll` together based on the value returned by `f`. | |
Group order is not guaranteed. Element order inside each group is guaranteed | |
to be stable. Useful when the value given by `f` is not needed. Returns `nil` | |
for empty collections. | |
Example: | |
(= '([{:id 1 :name :a} {:id 1 :name :c}] | |
[{:id 2 :name :b}]) | |
(groups :id [{:id 1 :name :a} | |
{:id 2 :name :b} | |
{:id 1 :name :c}]))" | |
[f coll] | |
(vals (group-by f coll))) | |
(defn separate-2 | |
"Separate `coll` into two collections based on boolean value of `f`. | |
Returns a pair [those-that-satisfy-f those-that-do-not-satisfy-f]. | |
Will always return a pair, so expect `nil` when one or both values to not satifsy `f`. | |
Guarantees stable order of each collection." | |
[pred coll] | |
(let [g (group-by (comp boolean pred) coll)] | |
[(get g true) (get g false)])) | |
(defn adjacent-pairs | |
"Returns pairs of adjacent (to the right) elements in `coll`. | |
Example: | |
(= [[1 2] [2 3]] | |
(adjacent-pairs [1 2 3]))" | |
[coll] | |
(mapv vector coll (rest coll))) | |
(defn combinations-2 | |
"Returns a lazy sequence of all possible pairs in `coll`. | |
Returns `nil` if no pairs exist." | |
[coll] | |
(when (seq coll) | |
(let [[x & rst] coll | |
other (lazy-seq (combinations-2 rst))] | |
(seq | |
(lazy-cat (map vector (repeat x) rst) | |
other))))) | |
(defn splits-by | |
"Splits `coll` into multiple groups each time `split-fn` returns `true`." | |
[split-fn coll] | |
(loop [coll coll | |
splits []] | |
(if-not (seq coll) | |
splits | |
(let [[f l] (split-with (complement split-fn) coll)] | |
(recur (rest l) (conj splits f)))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.coll-test | |
(:require | |
[clojure.test :refer [deftest is testing]] | |
[clojure.test.check.clojure-test :refer [defspec]] | |
[clojure.test.check.generators :as gen] | |
[clojure.test.check.properties :as prop] | |
[my.company.coll :as sut])) | |
(deftest coll-any? | |
(is (false? (sut/any? identity nil))) | |
(is (false? (sut/any? identity []))) | |
(is (false? (sut/any? identity [nil false]))) | |
(is (false? (sut/any? even? [1 3 5]))) | |
(is (false? (sut/any? false? [true true]))) | |
(is (true? (sut/any? odd? [1]))) | |
(is (true? (sut/any? nil? [1 nil 3]))) | |
(is (true? (sut/any? false? [true true false]))) | |
(is (true? (sut/any? identity [1 2 3])))) | |
(defspec includes?-always-returns-a-boolean | |
(prop/for-all | |
[x gen/any | |
coll (gen/one-of [(gen/vector gen/any) | |
(gen/list gen/any)])] | |
(boolean? (sut/includes? x coll)))) | |
(defspec includes?-must-return-true-if-collection-includes-element | |
(prop/for-all | |
[x gen/any | |
coll (gen/one-of [(gen/vector gen/any) | |
(gen/list gen/any)])] | |
(true? (sut/includes? x (conj coll x))))) | |
(deftest includes? | |
(is (false? (sut/includes? 1 nil))) | |
(is (false? (sut/includes? 1 []))) | |
(is (false? (sut/includes? 1 [2]))) | |
(is (false? (sut/includes? 1 [:a :b]))) | |
(is (false? (sut/includes? nil [:a :b]))) | |
(is (true? (sut/includes? 1 [1 2 3]))) | |
(is (true? (sut/includes? 1 [3 2 1]))) | |
(is (true? (sut/includes? :a [:b :a :c]))) | |
(is (true? (sut/includes? nil [1 nil 3])))) | |
(deftest elem | |
(is (nil? (sut/elem some? nil))) | |
(is (nil? (sut/elem some? []))) | |
(is (nil? (sut/elem some? [nil]))) | |
(is (nil? (sut/elem true? [false]))) | |
(is (true? (sut/elem true? [false true]))) | |
(is (= 1 (sut/elem #(= 1 %) [1 2 3]))) | |
(is (= 1 (sut/elem #(= 1 %) [3 2 1]))) | |
(is (= :a (sut/elem #(= :a %) [:b :a :c]))) | |
(is (= :a (sut/elem #(= :a %) [:b :a :c :a :e :a])))) | |
(defspec groups-size-returned<=coll-size | |
(prop/for-all | |
[coll (gen/one-of [(gen/vector gen/any) | |
(gen/list gen/any)])] | |
(<= (count (sut/groups some? coll)) | |
(count coll)))) | |
(defspec groups-stable-order-inside-each-group | |
(prop/for-all | |
[coll (gen/fmap sort (gen/list gen/large-integer))] | |
(every? #(= % (sort %)) | |
(sut/groups pos-int? coll)))) | |
(deftest groups | |
(is (nil? (sut/groups some? nil))) | |
(is (nil? (sut/groups some? []))) | |
(is (= '([1]) (sut/groups some? [1]))) | |
(is (= '([1 2]) (sut/groups some? [1 2]))) | |
(is (= '([1 2] [nil]) (sut/groups some? [1 nil 2]))) | |
(is (= '([nil] [1 2]) (sut/groups some? [nil 1 2]))) | |
(is (= '([{:id 1 :name :a} {:id 1 :name :c}] | |
[{:id 2 :name :b}]) | |
(sut/groups :id [{:id 1 :name :a} | |
{:id 2 :name :b} | |
{:id 1 :name :c}])))) | |
(defspec separate-2-always-returns-a-pair | |
(prop/for-all | |
[coll (gen/one-of [(gen/vector gen/any) | |
(gen/list gen/any) | |
(gen/set gen/any)])] | |
(= 2 (count (sut/separate-2 some? coll))))) | |
(defspec separate-2-stable-order-inside-each-group | |
(prop/for-all | |
[coll (gen/fmap sort (gen/list gen/large-integer))] | |
(let [[a b] (sut/separate-2 pos-int? coll)] | |
(and (= a (seq (sort a))) | |
(= b (seq (sort b))))))) | |
(defspec separate-2-works-even-if-pred-doesnt-return-a-boolean | |
(prop/for-all | |
[coll (gen/one-of [(gen/vector gen/any) | |
(gen/list gen/any) | |
(gen/set gen/any)])] | |
(let [[a b] (sut/separate-2 some? coll)] | |
(and (every? some? a) | |
(every? nil? b))))) | |
(deftest separate-2 | |
(is (= [nil nil] (sut/separate-2 some? nil))) | |
(is (= [nil nil] (sut/separate-2 some? []))) | |
(is (= ['(1) nil] (sut/separate-2 some? [1]))) | |
(is (= [nil '(nil)] (sut/separate-2 some? [nil]))) | |
(is (= ['(:a) '(1)] (sut/separate-2 keyword? [:a 1]))) | |
(is (= ['(:a :b :c) '(1 2 3)] (sut/separate-2 keyword? [:a 1 :b 2 :c 3])))) | |
(deftest adjacent-pairs | |
(is (= [] (sut/adjacent-pairs nil))) | |
(is (= [] (sut/adjacent-pairs []))) | |
(is (= [] (sut/adjacent-pairs [1]))) | |
(is (= [[1 2]] (sut/adjacent-pairs [1 2]))) | |
(is (= [[1 2] [2 3]] (sut/adjacent-pairs [1 2 3]))) | |
(is (= [[1 2] [2 3] [3 4]] (sut/adjacent-pairs [1 2 3 4])))) | |
(deftest combinations-2 | |
(is (nil? (sut/combinations-2 nil))) | |
(is (nil? (sut/combinations-2 []))) | |
(is (nil? (sut/combinations-2 [1]))) | |
(is (= '([1 2]) (sut/combinations-2 [1 2]))) | |
(is (= '([1 2] [1 3] [2 3]) | |
(sut/combinations-2 [1 2 3]))) | |
(is (= '([1 2] [1 3] [1 4] [2 3] [2 4] [3 4]) | |
(sut/combinations-2 [1 2 3 4]))) | |
(testing "is lazy" | |
(is (= '([0 1] [0 2] [0 3] [0 4] [0 5]) | |
(take 5 (sut/combinations-2 (range)))))) | |
(testing "does not consume the stack" | |
(is (= 100000 | |
(count (take 100000 (sut/combinations-2 (range 1000)))))))) | |
(deftest splits-by | |
(is (= [] (sut/splits-by keyword? []))) | |
(is (= [[1 2]] (sut/splits-by keyword? [1 2]))) | |
(is (= [[1 2] [3 4]] (sut/splits-by keyword? [1 2 :here 3 4]))) | |
(is (= [[1] [2] [3] [4]] (sut/splits-by keyword? [1 :here 2 :here 3 :here 4]))) | |
(is (= [[1]] (sut/splits-by keyword? [1 :here]))) | |
(is (= [[1] []] (sut/splits-by keyword? [1 :here :here]))) | |
(is (= [[] [1]] (sut/splits-by keyword? [:here 1]))) | |
(is (= [[] [] [1]] (sut/splits-by keyword? [:here :here 1]))) | |
(is (= [[1] [] [2]] (sut/splits-by keyword? [1 :here :here 2])))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.inout | |
"Missing functions in `clojure.java.io`." | |
(:require | |
[clojure.edn :as edn] | |
[clojure.java.io :as io])) | |
(defn read-bytes [streamable] | |
(with-open [is (io/input-stream streamable)] | |
(.readAllBytes is))) | |
(defn read-edn [readable] | |
(with-open [r (io/reader readable)] | |
(edn/read-string (slurp r)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.kw | |
"Missing functions in `clojure.core` that accept a keyword." | |
(:refer-clojure :exclude [str])) | |
(defn str | |
"Makes a string out of `k` so that it can be shared to non-clojure world. | |
Preserves namespace info if present." | |
[k] | |
(if (qualified-keyword? k) | |
(format "%s/%s" (namespace k) (name k)) | |
(name k))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.kw-test | |
(:require | |
[clojure.test :refer [deftest is]] | |
[my.company.kw :as sut])) | |
(deftest str-test | |
(is (= "a" (sut/str :a))) | |
(is (= "test/a" (sut/str :test/a)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.map | |
"Missing functions in `clojure.core` that accept persistent map. | |
Argument names and expected positions use the same convention as `clojure.core`.") | |
(defn map-key | |
"Transform by calling `f` on each key of `m`. | |
Always returns a new map of the same category as `m`." | |
[m f] | |
(->> m | |
(map (fn [[k v]] [(f k) v])) | |
(into (empty m)))) | |
(defn map-val | |
"Transform by calling `f` on each value of `m`. | |
Always returns a new map of the same category as `m`." | |
[m f] | |
(->> m | |
(map (fn [[k v]] [k (f v)])) | |
(into (empty m)))) | |
(defn filter-key | |
"Filter entries of `m` satisfying `(pred k)`. | |
Always returns a new map of the same category as `m`." | |
[m pred] | |
(->> m | |
(filter (comp pred key)) | |
(into (empty m)))) | |
(defn filter-val | |
"Filter entries of `m` satisfying `(pred v)`. | |
Always returns a new map of the same category as `m`." | |
[m pred] | |
(->> m | |
(filter (comp pred val)) | |
(into (empty m)))) | |
(defn remove-key | |
"Removes entries of `m` satisfying `(pred k)`. | |
Always returns a new map of the same category as `m`." | |
[m pred] | |
(filter-key m (comp not pred))) | |
(defn remove-val | |
"Removes entries of `m` satisfying `(pred v)`. | |
Always returns a new map of the same category as `m`." | |
[m pred] | |
(filter-val m (comp not pred))) | |
(defn remove-keys | |
"Opposite of `clojure.core/select-keys`." | |
[m ks] | |
(apply dissoc m ks)) | |
(defn dissoc-in | |
"Dissociate a key with path `ks` in a nested associated structure `m`." | |
[m ks] | |
(cond | |
(zero? (count ks)) m | |
(= 1 (count ks)) (dissoc m (first ks)) | |
:else | |
(let [[k & rks] ks] | |
(if (contains? m k) | |
(update m k #(dissoc-in % rks)) | |
m)))) | |
(defn update-when | |
"Update the value of `k` in `m` with `f` only when `(pred v)`." | |
[m k pred f] | |
(if (pred (get m k)) | |
(update m k f) | |
m)) | |
(defn update-if | |
"Update the value of `k` in `m` with `f` only if `k` exists." | |
[m k f] | |
(if (contains? m k) | |
(update m k f) | |
m)) | |
(defn assoc-if | |
"Override the value of `k` in `m` with `v` only if `k` exists." | |
[m k v] | |
(if (contains? m k) | |
(assoc m k v) | |
m)) | |
(defn submap? | |
"Does `m` contain all keys and their vals of `n`?" | |
[m n] | |
(= n (select-keys m (keys n)))) | |
(defn unqualified | |
"Convert map keys to unqualified keywords. | |
Assumes map keys are keywords." | |
[m] | |
(map-key m (comp keyword name))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.map-test | |
(:require | |
[clojure.test :refer [deftest is]] | |
[clojure.test.check.clojure-test :refer [defspec]] | |
[clojure.test.check.generators :as gen] | |
[clojure.test.check.properties :as prop] | |
[my.company.map :as sut])) | |
(defspec map-key-works-on-any-map-category | |
(prop/for-all | |
[m (gen/hash-map gen/any gen/any)] | |
(= (type m) (type (sut/map-key m identity))))) | |
(deftest map-key | |
(is (= {} (sut/map-key {} inc))) | |
(is (= {1 :b} (sut/map-key {1 :a 2 :b} (constantly 1)))) | |
(is (= {2 :a 3 :b} (sut/map-key {1 :a 2 :b} inc))) | |
(is (sorted? (sut/map-key (sorted-map :a 1 :b 2) identity)))) | |
(defspec map-val-works-on-any-map-category | |
(prop/for-all | |
[m (gen/hash-map gen/any gen/any)] | |
(= (type m) (type (sut/map-val m identity))))) | |
(deftest map-val | |
(is (= {} (sut/map-val {} inc))) | |
(is (= {1 :b} (sut/map-key {1 :a 2 :b} (constantly 1)))) | |
(is (= {:a 2 :b 3} (sut/map-val {:a 1 :b 2} inc))) | |
(is (sorted? (sut/map-val (sorted-map :a 1 :b 2) identity)))) | |
(defspec filter-key-works-on-any-map-category | |
(prop/for-all | |
[m (gen/hash-map gen/any gen/any)] | |
(= (type m) (type (sut/filter-key m some?))))) | |
(deftest filter-key | |
(is (= {} (sut/filter-key {} pos-int?))) | |
(is (= {1 :a} (sut/filter-key {1 :a -1 :b} pos-int?))) | |
(is (sorted? (sut/filter-key (sorted-map :a 1 :b 2) some?)))) | |
(defspec filter-val-works-on-any-map-category | |
(prop/for-all | |
[m (gen/hash-map gen/any gen/any)] | |
(= (type m) (type (sut/filter-val m some?))))) | |
(deftest filter-val | |
(is (= {} (sut/filter-val {} pos-int?))) | |
(is (= {:a 1} (sut/filter-val {:a 1 :b -1} pos-int?))) | |
(is (sorted? (sut/filter-val (sorted-map :a 1 :b 2) some?)))) | |
(defspec remove-key-works-on-any-map-category | |
(prop/for-all | |
[m (gen/hash-map gen/any gen/any)] | |
(= (type m) (type (sut/remove-key m some?))))) | |
(deftest remove-key | |
(is (= {} (sut/remove-key {} pos-int?))) | |
(is (= {-1 :b} (sut/remove-key {1 :a -1 :b} pos-int?))) | |
(is (sorted? (sut/remove-key (sorted-map :a 1 :b 2) some?)))) | |
(defspec remove-val-works-on-any-map-category | |
(prop/for-all | |
[m (gen/hash-map gen/any gen/any)] | |
(= (type m) (type (sut/remove-val m some?))))) | |
(deftest remove-val | |
(is (= {} (sut/remove-val {} pos-int?))) | |
(is (= {:b -1} (sut/remove-val {:a 1 :b -1} pos-int?))) | |
(is (sorted? (sut/remove-val (sorted-map :a 1 :b 2) some?)))) | |
(deftest remove-keys | |
(is (= {} (sut/remove-keys {} #{}))) | |
(is (= {} (sut/remove-keys {:a 1} #{:a}))) | |
(is (= {:a 1} (sut/remove-keys {:a 1} #{}))) | |
(is (= {:a 1} (sut/remove-keys {:a 1} #{:b})))) | |
(deftest dissoc-in | |
(is (= {:a 1} (sut/dissoc-in {:a 1} []))) | |
(is (= {:a 1} (sut/dissoc-in {:a 1} [:z]))) | |
(is (= {:a {:b 2}} (sut/dissoc-in {:a {:b 2}} [:a :z]))) | |
(is (= {:a {:b 2}} (sut/dissoc-in {:a {:b 2}} [:y :z]))) | |
(is (= {:a {:b {:c 3}}} (sut/dissoc-in {:a {:b {:c 3}}} [:a :y :z]))) | |
(is (= {} (sut/dissoc-in {:a 1} [:a]))) | |
(is (= {:b 2} (sut/dissoc-in {:a 1 :b 2} [:a]))) | |
(is (= {:a {}} (sut/dissoc-in {:a {:b 2}} [:a :b]))) | |
(is (= {:a {}} (sut/dissoc-in {:a {:b {:c 3}}} [:a :b]))) | |
(is (= {:a {:b {}}} (sut/dissoc-in {:a {:b {:c 3}}} [:a :b :c])))) | |
(deftest update-when | |
(is (= {} (sut/update-when {} :a string? #(str % "abc")))) | |
(is (= {:a nil} (sut/update-when {:a nil} :a string? #(str % "abc")))) | |
(is (= {:a 1} (sut/update-when {:a 1} :a string? #(str % "abc")))) | |
(is (= {:a "Xabc"} (sut/update-when {:a "X"} :a string? #(str % "abc"))))) | |
(deftest update-if | |
(is (= {} (sut/update-if {} :a inc))) | |
(is (= {:a 2} (sut/update-if {:a 1} :a inc))) | |
(is (= {:a "abc"} (sut/update-if {:a nil} :a #(str % "abc"))))) | |
(deftest assoc-if | |
(is (= {} (sut/assoc-if {} :a 1))) | |
(is (= {:a 2} (sut/assoc-if {:a 1} :a 2))) | |
(is (= {:a 1} (sut/assoc-if {:a nil} :a 1)))) | |
(deftest submap? | |
(is (true? (sut/submap? {} {}))) | |
(is (true? (sut/submap? {:a 1} {}))) | |
(is (true? (sut/submap? {:a 1 :b 2} {:a 1}))) | |
(is (true? (sut/submap? {:a 1 :b 2} {:a 1 :b 2}))) | |
(is (false? (sut/submap? {} {:a 1}))) | |
(is (false? (sut/submap? {:a 1} {:a 2}))) | |
(is (false? (sut/submap? {:a 1} {:b 1})))) | |
(deftest unqualified | |
(is (= {} (sut/unqualified {}))) | |
(is (= {:a 1} (sut/unqualified {:a 1}))) | |
(is (= {:a 1} (sut/unqualified {:user/a 1})))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.number | |
"Missing functions in `clojure.core` that accept a number." | |
(:import | |
(java.math RoundingMode))) | |
(defn constrain | |
"Make `x` fit the bounds `minimum` and `maximum`." | |
[minimum maximum x] | |
(max minimum (min maximum x))) | |
(defn set-scale | |
"Sets the scale of `x` to `n`. | |
`n` must be positive integer. Always returns a `double`. | |
See also `BigDecimal/setScale`." | |
^double [^Integer n x] | |
(-> x | |
(double) | |
(bigdec) | |
(.setScale n RoundingMode/HALF_UP) | |
(.doubleValue))) | |
;; https://en.wikipedia.org/wiki/Foot_(unit) | |
(def ^:private conversion-factor-ft->cm 30.48) | |
;; https://en.wikipedia.org/wiki/Pound_(mass) | |
(def ^:private conversion-factor-lb->kg 0.45359237) | |
(defn cm->ft [x] | |
(/ x conversion-factor-ft->cm)) | |
(defn ft->cm [x] | |
(* x conversion-factor-ft->cm)) | |
(defn kg->lb [x] | |
(/ x conversion-factor-lb->kg)) | |
(defn lb->kg [x] | |
(* x conversion-factor-lb->kg)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.number-test | |
(:require | |
[clojure.test :refer [deftest is testing]] | |
[my.company.number :as sut])) | |
(deftest constrain | |
(is (= 1 (sut/constrain 1 9 1))) | |
(is (= 3 (sut/constrain 1 9 3))) | |
(is (= 9 (sut/constrain 1 9 9))) | |
(is (= -5 (sut/constrain -9 -1 -5))) | |
(is (= 0 (sut/constrain -9 9 0))) | |
(is (= 1 (sut/constrain 1 9 -1))) | |
(is (= 1 (sut/constrain 1 9 0))) | |
(is (= 9 (sut/constrain 1 9 10)))) | |
(deftest set-scale | |
(testing "doubles" | |
(is (= 0.0 (sut/set-scale 1 0.001))) | |
(is (= 0.3 (sut/set-scale 1 0.345))) | |
(is (= 0.5 (sut/set-scale 1 0.456))) | |
(is (= 0.6 (sut/set-scale 1 0.567))) | |
(is (= 0.57 (sut/set-scale 2 0.567))) | |
(is (= 1.35 (sut/set-scale 7 1.35)))) | |
(testing "non doubles" | |
(is (= 0.0 (sut/set-scale 1 0))) | |
(is (= 7.0 (sut/set-scale 1 7))) | |
(is (= 0.33 (sut/set-scale 2 (/ 1 3)))))) | |
(defn- close-to? [x y] | |
(<= (abs (- x y)) 1e-6)) | |
(deftest convert-cm-and-ft | |
(doseq [x [0 1 5 10 100 1e6 1e9 133.59 1/2 1/3 1/9 0.5123512]] | |
(is (close-to? x (sut/ft->cm (sut/cm->ft x)))) | |
(is (close-to? x (sut/cm->ft (sut/ft->cm x)))))) | |
(deftest convert-kg-and-lb | |
(doseq [x [0 1 5 10 100 1e6 1e9 133.59 1/2 1/3 1/9 0.5123512]] | |
(is (close-to? x (sut/lb->kg (sut/kg->lb x)))) | |
(is (close-to? x (sut/kg->lb (sut/lb->kg x)))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.pair | |
"A pair is a vector of size 2. | |
Functions here preserve the vector form.") | |
(defn map-1 | |
"Transform first element of `p` using `f`." | |
[[a b] f] | |
[(f a) b]) | |
(defn map-2 | |
"Transform second element of `p` using `f`." | |
[[a b] f] | |
[a (f b)]) | |
(defn map-3 | |
"Transform first element of `p` using `f1` and second element of `p` using `f2`." | |
[[a b] f1 f2] | |
[(f1 a) (f2 b)]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.pair-test | |
(:require | |
[clojure.test :refer [deftest is]] | |
[my.company.pair :as sut])) | |
(deftest map-1 | |
(is (= [2 2] (sut/map-1 [1 2] inc)))) | |
(deftest map-2 | |
(is (= [1 3] (sut/map-2 [1 2] inc)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.pairs | |
"Pairs is a collection of `pair`." | |
(:require | |
[my.company.pair :as pair])) | |
(defn map-1 | |
"Transform first element of `ps` using `f`." | |
[f ps] | |
(map #(pair/map-1 % f) ps)) | |
(defn map-2 | |
"Transform second element of `ps` using `f`." | |
[f ps] | |
(map #(pair/map-2 % f) ps)) | |
(defn filter-1 | |
"Filter `ps` where first element satisfies `pred`." | |
[pred ps] | |
(filter (fn [[a _]] (pred a)) ps)) | |
(defn filter-2 | |
"Filter `ps` where second element satisfies `pred`." | |
[pred ps] | |
(filter (fn [[_ b]] (pred b)) ps)) | |
(defn firsts | |
"Take the first elements of `ps`." | |
[ps] | |
(map first ps)) | |
(defn seconds | |
"Take the second elements of `ps`." | |
[ps] | |
(map second ps)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.pairs-test | |
(:require | |
[clojure.test :refer [deftest is]] | |
[my.company.pairs :as sut])) | |
(deftest map-1 | |
(is (= [[:a 1] [:b 2]] | |
(sut/map-1 keyword [["a" 1] ["b" 2]])))) | |
(deftest map-2 | |
(is (= [[1 :a] [2 :b]] | |
(sut/map-2 keyword [[1 "a"] [2 "b"]])))) | |
(deftest filter-1 | |
(is (= [[:a 1]] | |
(sut/filter-1 #(= :a %) [[:a 1] [:b 2]])))) | |
(deftest filter-2 | |
(is (= [[:a 1]] | |
(sut/filter-2 #(= 1 %) [[:a 1] [:b 2]])))) | |
(deftest firsts | |
(is (= [:a :b] (sut/firsts [[:a 1] [:b 2]])))) | |
(deftest seconds | |
(is (= [1 2] (sut/seconds [[:a 1] [:b 2]])))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.text | |
"Missing functions in `clojure.string`. | |
This namespace deals with Java and Clojure strings, but is not named `string` | |
in order to avoid the clash with `clojure.string` namespace. | |
Argument names and expected positions use the same convention as `clojure.string`." | |
(:refer-clojure :exclude [double? int?]) | |
(:require | |
[clojure.string :as string]) | |
(:import | |
(java.net MalformedURLException URL) | |
(java.time DateTimeException LocalDate Year YearMonth))) | |
(defn try-as-int | |
"Try parsing `text` as an integer. | |
Returns `nil` on failure." | |
[text] | |
(try | |
(Integer/parseInt text) | |
(catch NumberFormatException _ | |
nil))) | |
(defn try-as-double | |
"Try parsing `text` as a double. | |
Returns `nil` on failure." | |
[text] | |
(when text | |
(try | |
(Double/parseDouble text) | |
(catch NumberFormatException _ | |
nil)))) | |
(defn try-as-finite-double | |
"Try parsing `text` as a finite double. | |
Returns `nil` on failure. Rejects infinities and not-a-number." | |
[text] | |
(when-not (#{"NaN" "Infinity" "-Infinity"} (some-> text string/trim)) | |
(try-as-double text))) | |
(defn try-as-boolean | |
"Try parsing `text` as a boolean. | |
Returns `nil` on failure." | |
[text] | |
(let [t (some-> text string/trim)] | |
(cond | |
(= "true" t) true | |
(= "false" t) false | |
:else nil))) | |
(defn try-as-year | |
"Try parsing `text` as a `java.time.Year`. | |
Returns `nil` on failure." | |
[text] | |
(when text | |
(try | |
(Year/parse text) | |
(catch DateTimeException _ | |
nil)))) | |
(defn try-as-year-month | |
"Try parsing `text` as a `java.time.YearMonth`. | |
Returns `nil` on failure." | |
[text] | |
(when text | |
(try | |
(YearMonth/parse text) | |
(catch DateTimeException _ | |
nil)))) | |
(defn try-as-date | |
"Try parsing `text` as a `java.time.LocalDate`. | |
Returns `nil` on failure." | |
[text] | |
(when text | |
(try | |
(LocalDate/parse text) | |
(catch DateTimeException _ | |
nil)))) | |
(defn int? [text] | |
(boolean (try-as-int text))) | |
(defn double? [text] | |
(boolean (try-as-finite-double text))) | |
(defn positive-double? [text] | |
(let [number (try-as-finite-double text)] | |
(boolean (and number (pos? number))))) | |
(defn year? [text] | |
(boolean (try-as-year text))) | |
(defn year-month? [text] | |
(boolean (try-as-year-month text))) | |
(defn date? [text] | |
(boolean (try-as-date text))) | |
(defn url? [text] | |
(try | |
(URL. text) | |
true | |
(catch MalformedURLException _ | |
false))) | |
(defn as-number-if-possible | |
"Best effort coerse `text` into a number or simply return `text` if not possible. | |
Will try to return the most restrictive number type possible. E.g. an integer | |
instead of returning a double. Infinities and not-a-number are not considered | |
as numbers." | |
[text] | |
(or (try-as-int text) | |
(try-as-finite-double text) | |
text)) | |
(defn ellipsis | |
"Truncate extra characters from `text` with ..." | |
[text max-length] | |
(if (<= (count text) max-length) | |
text | |
(let [ellipsis "..." | |
up-to (max 0 (- max-length (count ellipsis)))] | |
(-> text | |
(subs 0 up-to) | |
(str ellipsis) | |
(subs 0 max-length))))) | |
(defn blank->empty [text] | |
(if (string/blank? text) | |
"" | |
text)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.text-test | |
(:refer-clojure :exclude [double? int?]) | |
(:require | |
[clojure.test :refer [deftest is]] | |
[my.company.text :as sut]) | |
(:import | |
(java.time LocalDate Year YearMonth))) | |
(deftest try-as-int | |
(is (= -1 (sut/try-as-int "-1"))) | |
(is (= 0 (sut/try-as-int "0"))) | |
(is (= 1 (sut/try-as-int "1"))) | |
(is (= 1 (sut/try-as-int "+1"))) | |
(is (nil? (sut/try-as-int "1.1"))) | |
(is (nil? (sut/try-as-int "1e3"))) | |
(is (nil? (sut/try-as-int "0xFF"))) | |
(is (nil? (sut/try-as-int "NaN"))) | |
(is (nil? (sut/try-as-int "Infinity"))) | |
(is (nil? (sut/try-as-int "-Infinity"))) | |
(is (nil? (sut/try-as-int "abc"))) | |
(is (nil? (sut/try-as-int "abc123"))) | |
(is (nil? (sut/try-as-int ""))) | |
(is (nil? (sut/try-as-int nil)))) | |
(deftest try-as-double | |
(is (= -1.0 (sut/try-as-double "-1"))) | |
(is (= 0.0 (sut/try-as-double "0"))) | |
(is (= 1.0 (sut/try-as-double "1"))) | |
(is (= 1.0 (sut/try-as-double "+1"))) | |
(is (= -1.1 (sut/try-as-double "-1.1"))) | |
(is (= 1.1 (sut/try-as-double "1.1"))) | |
(is (= 0.1 (sut/try-as-double ".1"))) | |
(is (= -1000.0 (sut/try-as-double "-1e3"))) | |
(is (= 1000.0 (sut/try-as-double "1e3"))) | |
(is (= 1000.0 (sut/try-as-double "+1e3"))) | |
(is (= 0.001 (sut/try-as-double "1e-3"))) | |
(is (= 1.1 (sut/try-as-double " 1.1 "))) | |
(is (Double/isNaN (sut/try-as-double "NaN"))) | |
(is (Double/isNaN (sut/try-as-double " NaN "))) | |
(is (Double/isInfinite (sut/try-as-double "Infinity"))) | |
(is (Double/isInfinite (sut/try-as-double " Infinity "))) | |
(is (Double/isInfinite (sut/try-as-double "-Infinity"))) | |
(is (Double/isInfinite (sut/try-as-double " -Infinity "))) | |
(is (nil? (sut/try-as-double "1e2.5"))) | |
(is (nil? (sut/try-as-double "nan"))) | |
(is (nil? (sut/try-as-double "infinity"))) | |
(is (nil? (sut/try-as-double "-infinity"))) | |
(is (nil? (sut/try-as-double "0xFF"))) | |
(is (nil? (sut/try-as-double "0xFF.11"))) | |
(is (nil? (sut/try-as-double "abc"))) | |
(is (nil? (sut/try-as-double "abc123"))) | |
(is (nil? (sut/try-as-double ""))) | |
(is (nil? (sut/try-as-double nil)))) | |
(deftest try-as-finite-double | |
(is (= -1.0 (sut/try-as-finite-double "-1"))) | |
(is (= 0.0 (sut/try-as-finite-double "0"))) | |
(is (= 1.0 (sut/try-as-finite-double "1"))) | |
(is (= 1.0 (sut/try-as-finite-double "+1"))) | |
(is (= -1.1 (sut/try-as-finite-double "-1.1"))) | |
(is (= 1.1 (sut/try-as-finite-double "1.1"))) | |
(is (= 0.1 (sut/try-as-finite-double ".1"))) | |
(is (= -1000.0 (sut/try-as-finite-double "-1e3"))) | |
(is (= 1000.0 (sut/try-as-finite-double "1e3"))) | |
(is (= 1000.0 (sut/try-as-finite-double "+1e3"))) | |
(is (= 0.001 (sut/try-as-finite-double "1e-3"))) | |
(is (= 1.1 (sut/try-as-finite-double " 1.1 "))) | |
(is (nil? (sut/try-as-finite-double "NaN"))) | |
(is (nil? (sut/try-as-finite-double " NaN "))) | |
(is (nil? (sut/try-as-finite-double "Infinity"))) | |
(is (nil? (sut/try-as-finite-double " Infinity "))) | |
(is (nil? (sut/try-as-finite-double "-Infinity"))) | |
(is (nil? (sut/try-as-finite-double " -Infinity "))) | |
(is (nil? (sut/try-as-finite-double "1e2.5"))) | |
(is (nil? (sut/try-as-finite-double "nan"))) | |
(is (nil? (sut/try-as-finite-double "infinity"))) | |
(is (nil? (sut/try-as-finite-double "-infinity"))) | |
(is (nil? (sut/try-as-finite-double "0xFF"))) | |
(is (nil? (sut/try-as-finite-double "0xFF.11"))) | |
(is (nil? (sut/try-as-finite-double "abc"))) | |
(is (nil? (sut/try-as-finite-double "abc123"))) | |
(is (nil? (sut/try-as-finite-double ""))) | |
(is (nil? (sut/try-as-finite-double nil)))) | |
(deftest try-as-boolean | |
(is (true? (sut/try-as-boolean "true"))) | |
(is (true? (sut/try-as-boolean " true "))) | |
(is (false? (sut/try-as-boolean "false"))) | |
(is (false? (sut/try-as-boolean " false "))) | |
(is (nil? (sut/try-as-boolean nil))) | |
(is (nil? (sut/try-as-boolean ""))) | |
(is (nil? (sut/try-as-boolean "1"))) | |
(is (nil? (sut/try-as-boolean "0"))) | |
(is (nil? (sut/try-as-boolean "on"))) | |
(is (nil? (sut/try-as-boolean "off"))) | |
(is (nil? (sut/try-as-boolean "yes"))) | |
(is (nil? (sut/try-as-boolean "no"))) | |
(is (nil? (sut/try-as-boolean "abc")))) | |
(deftest try-as-year | |
(is (= (Year/of 2020) (sut/try-as-year "2020"))) | |
(is (= (Year/of 2) (sut/try-as-year "2"))) | |
(is (= (Year/of -1) (sut/try-as-year "-1"))) | |
(is (nil? (sut/try-as-year nil))) | |
(is (nil? (sut/try-as-year ""))) | |
(is (nil? (sut/try-as-year "abc"))) | |
(is (nil? (sut/try-as-year "2020-12"))) | |
(is (nil? (sut/try-as-year "2020-12-31")))) | |
(deftest try-as-year-month | |
(is (= (YearMonth/of 2020 1) (sut/try-as-year-month "2020-01"))) | |
(is (nil? (sut/try-as-year-month nil))) | |
(is (nil? (sut/try-as-year-month ""))) | |
(is (nil? (sut/try-as-year-month "abc"))) | |
(is (nil? (sut/try-as-year-month "2020"))) | |
(is (nil? (sut/try-as-year-month "2020-0"))) | |
(is (nil? (sut/try-as-year-month "2020-2"))) | |
(is (nil? (sut/try-as-year-month "2020-13"))) | |
(is (nil? (sut/try-as-year-month "2020-12-31")))) | |
(deftest try-as-date | |
(is (= (LocalDate/of 2020 1 25) (sut/try-as-date "2020-01-25"))) | |
(is (nil? (sut/try-as-date nil))) | |
(is (nil? (sut/try-as-date ""))) | |
(is (nil? (sut/try-as-date "abc"))) | |
(is (nil? (sut/try-as-date "2020"))) | |
(is (nil? (sut/try-as-date "2020-12"))) | |
(is (nil? (sut/try-as-date "2020-3-2"))) | |
(is (nil? (sut/try-as-date "2020-12-31-4")))) | |
(deftest as-number-if-possible | |
(is (= 0 (sut/as-number-if-possible "0"))) | |
(is (= 1 (sut/as-number-if-possible "1"))) | |
(is (= -1 (sut/as-number-if-possible "-1"))) | |
(is (= 1.5 (sut/as-number-if-possible "1.5"))) | |
(is (= -5.9 (sut/as-number-if-possible "-5.9"))) | |
(is (= Integer (type (sut/as-number-if-possible "0")))) | |
(is (= Double (type (sut/as-number-if-possible "1.1")))) | |
(is (= "" (sut/as-number-if-possible ""))) | |
(is (= nil (sut/as-number-if-possible nil))) | |
(is (= "Infinity" (sut/as-number-if-possible "Infinity"))) | |
(is (= "abc" (sut/as-number-if-possible "abc")))) | |
(deftest int? | |
(is (true? (sut/int? "1"))) | |
(is (true? (sut/int? "0"))) | |
(is (true? (sut/int? "-1"))) | |
(is (false? (sut/int? "1.0"))) | |
(is (false? (sut/int? "abc")))) | |
(deftest double? | |
(is (true? (sut/double? "-1"))) | |
(is (true? (sut/double? "0"))) | |
(is (true? (sut/double? "1"))) | |
(is (true? (sut/double? "+1"))) | |
(is (true? (sut/double? "-1.1"))) | |
(is (true? (sut/double? "1.1"))) | |
(is (true? (sut/double? ".1"))) | |
(is (true? (sut/double? "-1e3"))) | |
(is (true? (sut/double? "1e3"))) | |
(is (true? (sut/double? "+1e3"))) | |
(is (true? (sut/double? "1e-3"))) | |
(is (true? (sut/double? " 1.1 "))) | |
(is (false? (sut/double? "NaN"))) | |
(is (false? (sut/double? " NaN "))) | |
(is (false? (sut/double? "Infinity"))) | |
(is (false? (sut/double? " Infinity "))) | |
(is (false? (sut/double? "-Infinity"))) | |
(is (false? (sut/double? " -Infinity "))) | |
(is (false? (sut/double? "1e2.5"))) | |
(is (false? (sut/double? "nan"))) | |
(is (false? (sut/double? "infinity"))) | |
(is (false? (sut/double? "-infinity"))) | |
(is (false? (sut/double? "0xFF"))) | |
(is (false? (sut/double? "0xFF.11"))) | |
(is (false? (sut/double? "abc"))) | |
(is (false? (sut/double? "abc123"))) | |
(is (false? (sut/double? ""))) | |
(is (false? (sut/double? nil)))) | |
(deftest positive-double? | |
(is (true? (sut/positive-double? "1"))) | |
(is (true? (sut/positive-double? "+1"))) | |
(is (true? (sut/positive-double? "1.1"))) | |
(is (true? (sut/positive-double? ".1"))) | |
(is (true? (sut/positive-double? "1e3"))) | |
(is (true? (sut/positive-double? "+1e3"))) | |
(is (true? (sut/positive-double? "1e-3"))) | |
(is (true? (sut/positive-double? " 1.1 "))) | |
(is (false? (sut/positive-double? "-1"))) | |
(is (false? (sut/positive-double? "0"))) | |
(is (false? (sut/positive-double? "-1.1"))) | |
(is (false? (sut/positive-double? "-1e3"))) | |
(is (false? (sut/positive-double? "NaN"))) | |
(is (false? (sut/positive-double? " NaN "))) | |
(is (false? (sut/positive-double? "Infinity"))) | |
(is (false? (sut/positive-double? " Infinity "))) | |
(is (false? (sut/positive-double? "-Infinity"))) | |
(is (false? (sut/positive-double? " -Infinity "))) | |
(is (false? (sut/positive-double? "1e2.5"))) | |
(is (false? (sut/positive-double? "nan"))) | |
(is (false? (sut/positive-double? "infinity"))) | |
(is (false? (sut/positive-double? "-infinity"))) | |
(is (false? (sut/positive-double? "0xFF"))) | |
(is (false? (sut/positive-double? "0xFF.11"))) | |
(is (false? (sut/positive-double? "abc"))) | |
(is (false? (sut/positive-double? "abc123"))) | |
(is (false? (sut/positive-double? ""))) | |
(is (false? (sut/positive-double? nil)))) | |
(deftest year? | |
(is (true? (sut/year? "2020"))) | |
(is (false? (sut/year? "abc")))) | |
(deftest year-month? | |
(is (true? (sut/year-month? "2020-12"))) | |
(is (false? (sut/year-month? "abc")))) | |
(deftest date? | |
(is (true? (sut/date? "2020-12-31"))) | |
(is (false? (sut/date? "abc")))) | |
(deftest url? | |
(is (true? (sut/url? "http://company.com"))) | |
(is (true? (sut/url? "https://company.com"))) | |
(is (true? (sut/url? "file:///home/user/Downloads"))) | |
(is (false? (sut/url? "custom://abc"))) | |
(is (false? (sut/url? "abc"))) | |
(is (false? (sut/url? "a:b"))) | |
(is (false? (sut/url? "a//b")))) | |
(deftest ellipsis | |
(is (= "" (sut/ellipsis "" 0))) | |
(is (= "" (sut/ellipsis "" 5))) | |
(is (= "" (sut/ellipsis "a" 0))) | |
(is (= "a" (sut/ellipsis "a" 1))) | |
(is (= "a" (sut/ellipsis "a" 5))) | |
(is (= "" (sut/ellipsis "abc" 0))) | |
(is (= "." (sut/ellipsis "abc" 1))) | |
(is (= ".." (sut/ellipsis "abc" 2))) | |
(is (= "abc" (sut/ellipsis "abc" 3))) | |
(is (= "abc" (sut/ellipsis "abc" 5))) | |
(is (= "" (sut/ellipsis "abcdefghijkl" 0))) | |
(is (= "." (sut/ellipsis "abcdefghijkl" 1))) | |
(is (= ".." (sut/ellipsis "abcdefghijkl" 2))) | |
(is (= "..." (sut/ellipsis "abcdefghijkl" 3))) | |
(is (= "a..." (sut/ellipsis "abcdefghijkl" 4))) | |
(is (= "ab..." (sut/ellipsis "abcdefghijkl" 5))) | |
(is (= "abc..." (sut/ellipsis "abcdefghijkl" 6))) | |
(is (= "abcd..." (sut/ellipsis "abcdefghijkl" 7))) | |
(is (= "abcdefg..." (sut/ellipsis "abcdefghijkl" 10))) | |
(is (= "abcdefgh..." (sut/ellipsis "abcdefghijkl" 11))) | |
(is (= "abcdefghijkl" (sut/ellipsis "abcdefghijkl" 12))) | |
(is (= "abcdefghijkl" (sut/ellipsis "abcdefghijkl" 20)))) | |
(deftest blank->empty | |
(is (= "" (sut/blank->empty ""))) | |
(is (= "" (sut/blank->empty " "))) | |
(is (= "" (sut/blank->empty "\n"))) | |
(is (= "" (sut/blank->empty "\t"))) | |
(is (= "" (sut/blank->empty " \n \t \r\n "))) | |
(is (= "a" (sut/blank->empty "a"))) | |
(is (= " a " (sut/blank->empty " a ")))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.uuid | |
"Missing functions for java.util.UUID." | |
(:import java.util.UUID)) | |
(defn from-string [s] | |
(UUID/fromString s)) | |
(defn coerce | |
"Coerce `x` into java.util.UUID." | |
[x] | |
(cond | |
(string? x) (from-string x) | |
(uuid? x) x | |
:else (throw (ex-info "Don't know how to coerce a value into java.util.UUID" | |
{:value x | |
:type (type x)})))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.company.uuid-test | |
(:require | |
[clojure.test :refer [deftest is]] | |
[my.company.uuid :as sut]) | |
(:import | |
(clojure.lang ExceptionInfo))) | |
(deftest from-string | |
(is (uuid? (sut/from-string "f2fbebd7-2620-47ce-9262-506e4aaad754")))) | |
(deftest coerce | |
(is (uuid? (sut/coerce "f2fbebd7-2620-47ce-9262-506e4aaad754"))) | |
(is (uuid? (sut/coerce #uuid "f2fbebd7-2620-47ce-9262-506e4aaad754"))) | |
(is (thrown? IllegalArgumentException (sut/coerce "123"))) | |
(is (thrown? ExceptionInfo (sut/coerce 123)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yup
(comp boolean some)
will do the same thing! 👍And thanks for mentioning
clojure.test.check
. I neglected to mention that detail. 😄