Just Juxt #3: Pack a Sequence (4clojure #31)
Write a function which packs consecutive duplicates into sub-lists.
Here's a live testing area for you to play with. See if you can solve it with juxt
. Then check out the answer below!
(ns live.test
(:require [cljs.test :refer-macros [deftest is testing run-tests]]))
(defn pack [s]
)
(deftest test-31
(is (= (pack [1 1 2 1 1 1 3 3]) '((1 1) (2) (1 1 1) (3 3))))
(is (= (pack [:a :a :b :b :c]) '((:a :a) (:b :b) (:c))))
(is (= (pack [[1 2] [1 2] [3 4]]) '(([1 2] [1 2]) ([3 4])))))
(run-tests)
Here's how to do it:
(defn pack [[a & z :as s]]
(when a
(let [[p q] ((juxt take-while drop-while)
#(= a %) s)]
(cons p (pack q)))))
We will now follow our established pattern of dismantling the fn to see how it works at each step. First by just showing how destructuring works in Clojure:
(let [[a & z :as s]
[1 1 2 1 1 1 3 3]]
s)
This binds our seq to a local variable s
, and labels the first item a
and the rest of the coll z
:
(let [[a & z :as s]
[1 1 2 1 1 1 3 3]]
a)
(let [[a & z :as s] [1 1 2 1 1 1 3 3]]
z)
This destructuring pattern is simply a way to call first
and rest
on our seq and give them all extremely concise names, a
, z
, and s
.
Take a look at the meat of the solution, the juxt
. It is used to create one of the let
bindings:
((juxt take-while drop-while) #(= 1 %) [1 1 2 1 1 1 3 3])
What's with the double parens? It looks kinda weird, no?
The reason is that juxt
returns a fn
. Recall its docstring:
user=> (doc juxt)
-------------------------
clojure.core/juxt
([f] [f g] [f g h] [f g h & fs])
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)]
((juxt a b c) x) => [(a x) (b x) (c x)]
The juxt
returned our vector, [(1 1) (2 1 1 1 3 3)]
, containing a list with the first 2 dupes of our seq, followed by a list of the remaining items.
The trick is in our predicate fn, #(= a %)
, which is passed to take-while
and drop-while
via juxt
.
Now for the next operation:
(cons '(1 1) (pack '(2 1 1 1 3 3)))
By way of (when a
we are recursively ripping off the chunks of consecutive dupes and pack
-ing them into a list! What a juxt
-tastic endeavor!
Here we have seen juxt
used in a new way, that is, to define a let
binding to use destructuring. That's awesome!
And we'll have another party tomorrow! See you later.