Solving Clojure problems on 4ever-clojure (44/151)
I’ve started to learn Clojure by solving the problems over at 4ever-clojure. There are currently 151 problems on that site. I just solved number 44 and thought I’d take some time to reflect.
The conj
function confused me at first because I didn’t understand
its purpose, which is to ask a collection to make a new collection
with an element added to it in a place that makes sense to the
collection. When the collection is a vector it makes sense to add
the element to the end of it, but for sequences it’s more efficient to
add it to the front.
Problem 30 is about removing consecutive duplicates from a sequence.
For example, '(9 3 3 2 1 1 1 3 1)
should become '(9 3 2 1 3 1)
.
Here’s how I solved it.
(def p30a
(fn [x]
(reverse
(loop [source x acc '()]
(if (empty? source)
acc
(if (= (first source) (second source))
(recur (drop 1 source) acc)
(recur (drop 1 source) (conj acc (first source)))))))))
Needlessly complicated, you say? I agree. “I’ll come back to this later and try to write a shorter version”, was what I was going to say but of course I couldn’t let it go so here’s my second attempt.
(def p30b
(fn [A]
(reverse
(reduce
#(if (= (first %1) %2) %1 (conj %1 %2))
'()
A))))
I got rid of one loop
and two recur
:s at the cost of introducing
one reduce
, which is an improvement, I think.
Problem 31: pack consecutive duplicates into sub-lists. For example,
'(1 1 2 1 1 1 3 3)
should become '((1 1) (2) (1 1 1) (3 3))
. Not
too happy with how I solved this one either.
(def p31
(fn [x]
(reverse
(loop [lst x, acc '()]
(if (empty? lst)
acc
(let [dacc (take-while (partial = (first lst)) lst)]
(recur (drop (count dacc) lst) (conj acc dacc))))))))
I was really into loop
and recur
at this point. It looks terrible,
there must be a much simpler solution but I’ll leave it for now.
Problem 39: Write a function which takes two sequences and returns the first item from each, then the second item from each, then the third, etc. I managed to produce a good solution to this one, I think.
(def p39
(fn [A B]
(mapcat vector A B)))
Looks great, if I do say so myself. I got there by realizing that a
map
function can operate on multiple collections at once, and then I
just happened to find mapcat
which conveniently concat:s the map
result.
Problem 41: drop every Nth item from a sequence. I don’t love what I came up with.
(def p41
(fn [A n]
(map
#(nth A %)
(filter
#(< 0 (mod (inc %) n))
(range (count A))))))
At least I’m just using plain functions like map
and filter
.
Problem 42: factorial function. Another one I’m pretty proud of.
(def p42
(fn [x]
(reduce * (map inc (range x)))))
I have no idea how that could be improved but we’ll see.
Problem 43: reverse the interleave process into x number of
subsequences. For example, '(1 2 3 4 5 6) 2
should become '((1 3 5) (2 4 6))
. This is the first “medium” difficulty problem I solved, all
the others were “easy” or “elementary”.
(def p43
(fn [A n]
(let [steps (filter #(= 0 (mod % n)) (range (count A)))]
(for [i (range n)]
(map #(nth A (+ i %)) steps)))))
I’m not loving the for
call, feels like there should be a cleaner
solution.
Problem 44: rotate a sequence in either direction. For example, 2 '(1 2 3 4 5)
should become '(3 4 5 1 2)
, and -2 '(1 2 3 4 5)
should
become '(4 5 1 2 3)
. My first solution p44a felt a little clunky so
I wrote p44b that uses a different approach.
(def p44a
(fn [d A]
(let [nA (count A)
s (map #(mod (+ % d) nA) (range nA))]
(map #(nth A %) s))))
(def p44b
(fn [d A]
(let [x (mod d (count A))]
(concat (drop x A) (take x A)))))