August 31, 2018
Just Juxt #17: Group a Sequence (4clojure #63)
Given a function f and a sequence s, write a function which returns a map. The keys should be the values of f applied to each item in s. The value at each key should be a vector of corresponding items in the order they appear in s.
...but without using group-by
.
(ns live.test
(:require [cljs.test :refer-macros [deftest is testing run-tests]]))
(defn group-seq [f s]
)
(deftest test-63
(is (= (group-seq #(> % 5) #{1 3 6 8}) {false [1 3], true [6 8]}))
(is (= (group-seq #(apply / %) [[1 2] [2 4] [4 6] [3 6]]) {1/2 [[1 2] [2 4] [3 6]], 2/3 [[4 6]]}))
(is (= (group-seq count [[1] [1 2] [3] [1 2 3] [2 3]]) {1 [[1] [3]], 2 [[1 2] [2 3]], 3 [[1 2 3]]})))
(run-tests)
To better understand the group-by
fn that we are re-implementing, let's look at its source
, as well as some examples from ClojureDocs:
user=> (source group-by)
(defn group-by
"Returns a map of the elements of coll keyed by the result of
f on each element. The value at each key will be a vector of the
corresponding elements, in the order they appeared in coll."
{:added "1.2"
:static true}
[f coll]
(persistent!
(reduce
(fn [ret x]
(let [k (f x)]
(assoc! ret k (conj (get ret k []) x))))
(transient {}) coll)))
We can group strings by their length:
(group-by count ["a" "as" "asd" "aa" "asdf" "qwer"])
Group integers by a predicate:
(group-by odd? (range 10))
Group by a primary key:
(group-by :user-id [{:user-id 1 :uri "/"}
{:user-id 2 :uri "/foo"}
{:user-id 1 :uri "/account"}])
Hey... how about if we use juxt
to group by multiple criteria:
(def words ["Air" "Bud" "Cup" "Awake" "Break" "Chunk" "Ant" "Big" "Check"])
(group-by (juxt first count) words)
Let's look at each of our unit tests with group-by
:
(group-by #(> % 5) #{1 3 6 8})
(group-by #(apply / %) [[1 2] [2 4] [4 6] [3 6]])
(group-by count [[1] [1 2] [3] [1 2 3] [2 3]])
The juxtification:
(defn group-seq [f s]
(->> (map (juxt f identity) s)
(reduce (fn [m [k v]]
(assoc m k (conj (get m k []) v)))
{})))
(run-tests)
Breakdown:
(->> (map (juxt #(> % 5) identity) #{1 3 6 8})
(reduce (fn [m [k v]]
(assoc m k (conj (get m k []) v)))
{}))
Take a look at each part:
(map (juxt #(> % 5) identity) #{1 3 6 8})
(reduce (fn [m [k v]]
(assoc m k (conj (get m k []) v)))
{} '([false 1] [false 3] [true 6] [true 8]))