Avi Drucker / May 02 2021
Remix of Clojure by Nextjournal
Clojure Koans 22 Group-By Notebook
Original Clojure Koans: https://github.com/functional-koans/clojure-koans/
Spoilers Alert
{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
;; complient is used for autocompletion
;; add your libs here (and restart the runtime to pick up changes)
compliment/compliment {:mvn/version "0.3.9"}}}
deps.edn
Extensible Data Notation
{:hello (clojure-version)}
0.1s
Clojure
Initial Thoughts
This was the "missing piece" for me from yesterday's notebook :)
(defn get-odds-and-evens [coll]
(let [{odds true evens false} (group-by odd? coll)]
[odds evens]))
Clojure
;; "To categorize a collection by some function, use group-by."
(= {5 ["hello" "world"] 3 ["foo" "bar"]}
(group-by count ["hello" "world" "foo" "bar"]))
;; description: Group strings of a vector by their length into a hashmap where the key:value pairs are word-length to vector-of-words
;; exercise 22001: Create a map of words by length where the key is the word length and the value is all words of said length from the orgiginal collection
;; skill 22001: Bucket items from a collection into a hashmap by means of a predefined function and said collection to the `group-by` function
Clojure
;; "You can simulate filter + remove in one pass"
(= (get-odds-and-evens [1 2 3 4 5])
((juxt filter remove) odd? [1 2 3 4 5])
[[1 3 5] [2 4]])
;; question 22001: What does the `juxt` function do?
;; answer 22001a: From the docs:
;; "Takes a set of functions and returns a fn that is the juxtaposition
;; of those fns. The returned fn takes a variable number of args, and
;; returns a vector containing the result of applying each fn to the
;; args (left-to-right).
;; ((juxt a b c) x) => [(a x) (b x) (c x)]"
;; question 22002: What does the `remove` function do?
;; answer 22002a: From the docs:
;; "Returns a lazy sequence of the items in coll for which
;; (pred item) returns logical false. pred must be free of side-effects.
;; Returns a transducer when no collection is provided."
Clojure
;; "You can also group by a primary key"
(= {1 [{:id 1 :name "Bob"} {:id 1 :last-name "Smith"}]
2 [{:id 2 :name "Jennifer"}]}
(group-by :id [{:id 1 :name "Bob"}
{:id 2 :name "Jennifer"}
{:id 1 :last-name "Smith"}]))
;; skill 22002: Bucket items from a collection of hashmaps into a hashmap by passing a primary "matching key".
Clojure
;; "But be careful when you group by a non-required key"
(= {"Bob" [{:name "Bob" :id 1}]
"Jennifer" [{:name "Jennifer" :id 2}]
nil [{:last-name "Smith" :id 1}]}
(group-by :name [{:id 1 :name "Bob"}
{:id 2 :name "Jennifer"}
{:id 1 :last-name "Smith"}]))
;; skill 22003: Bucket items from a collection of hashmaps by passing a non-required (non-primary) "matching key" into a hashmap.
;; question 2203: What is an example of the follow: the "gotcha" when using `group-by` on a collection where the search key is non-existant (it will group under `nil` instead)
Clojure
;; "The true power of group-by comes with custom functions"
(= {:naughty-list [{:name "Jimmy" :bad true}
{:name "Joe" :bad true}]
:nice-list [{:name "Jack" :bad false}]}
(group-by (if (:bad %) :naughty-list :nice-list)
[{:name "Jimmy" :bad true}
{:name "Jane" :bad false}
{:name "Joe" :bad true}]))
;; skill 22004: Bucket items from a collection into a hashmap by a custom predicate function mapped over said collection.
Clojure
Closing Thoughts
I'd like to have simpler and clearer language to describe what I am doing with group by. Also, what is another way to say predicate? To-do's for next time:
[ ] Read: https://www.tutorialspoint.com/clojure/clojure_predicates.htm
[ ] Read: https://practicalli.github.io/clojure/clojure-spec/data/predicate-specifications.html
[ ] Read: https://www.cognitect.com/blog/2016/8/9/focus-on-spec-predicates
[ ] Read: https://danielcompton.net/2017/03/31/clojure-nil-predicates