Idiomatic sequence slice in Clojure

Mirzhan Irkegulov picture Mirzhan Irkegulov · Aug 22, 2012 · Viewed 10k times · Source

In Python, there is a convenient way of taking parts of a list called "slicing":

a = [1,2,3,4,5,6,7,8,9,10] # ≡ a = range(1,10)
a[:3] # get first 3 elements
a[3:] # get all elements except the first 3
a[:-3] # get all elements except the last 3
a[-3:] # get last 3 elements
a[3:7] # get 4 elements starting from 3rd (≡ from 3rd to 7th exclusive)
a[3:-3] # get all elements except the first 3 and the last 3

Playing with clojure.repl/doc in Clojure, I found equivalents for all of them but I'm not sure they are idiomatic.

(def a (take 10 (iterate inc 1)))
(take 3 a)
(drop 3 a)
(take (- (count a) 3) a)
(drop (- (count a) 3) a)
(drop 3 (take 7 a))
(drop 3 (take (- (count a) 3) a))

My question is: how to slice sequences in Clojure? In other words, what is the correct way to return different parts of a sequence?

Answer

DaoWen picture DaoWen · Aug 22, 2012

You can simplify all the ones using count by using take-last or drop-last instead:

(def a (take 10 (iterate inc 1)))
(take 3 a) ; get first 3 elements
(drop 3 a) ; get all elements except the first 3
(drop-last 3 a) ; get all elements except the last 3
(take-last 3 a) ; get last 3 elements
(drop 3 (take 7 a)) ; get 4 elements starting from 3
(drop 3 (drop-last 3 a)) ; get all elements except the first and the last 3

And as suggested in the comments below, you can use the ->> macro to "thread" several operation together. For example, the last two lines could also be written like this:

(->> a (take 7) (drop 3)) ; get 4 elements starting from 3
(->> a (drop-last 3) (drop 3)) ; get all elements except the first and the last 3

I think the two methods are both very readable if you are only applying two operations to a list, but when you have a long string like take, map, filter, drop, first then using the ->> macro can make the code much easier to read and probably even easier for to write.