Rotating a sequence
The problem:
Write a function which can rotate a sequence in either direction.
Such that:
(= (your-fn 2 [1 2 3 4 5]) '(3 4 5 1 2))
and
(= (your-fn -2 [1 2 3 4 5]) '(4 5 1 2 3))
My solution
A recursive loop where the function that takes the loop closer to completion and the function that recurses on the collection differ depending on we’re rotating “backwards” or “forwards”.
It is ugly in the case of negative values because the into
is O(n), and that’s called n times.
(fn [n v]
(if (= n 0)
v
(let [f (if (pos? n) dec inc)
c (if (pos? n)
(conj (vec (rest v)) (first v))
(into [(last v)] (take (- (count v) 1) v)))]
(recur (f n) c))))
Two better solutions and why I missed them
chouser’s solution:
#(let [c (count %2)] (take c (drop (mod % c) (cycle %2))))
I was missing two key insights that are required for this more elegant
solution. One is “thinking lazy” to create an infinite seq of the passed-in
sequence repeated over and over. Once you have you just need to put your
(count n)
long “window” in the right place.
This is where mod
comes in. (mod % c)
gives you the number of items to
skip before placing the front of the “window”. This skipping and placement of
the “window” is accomplished by the paired take
and drop
. It would not
have occurred to me to drop
from an infinite sequence, because I neglected to
“think lazy” and understand that that concept isn’t realized until the take
is attempted.
quant1’s solution:
(fn [n x] (let [idx (mod n (count x))]
(concat (drop idx x) (take idx x))))
I started down this path, but because the mod
insight did not occur to me, I
gave up after being unable to take
or drop
negative numbers. I messed with
subvec
for a few mins before implementing my solution.