28

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?

omiel
  • 1,573
  • 13
  • 16
Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166

4 Answers4

35

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.

omiel
  • 1,573
  • 13
  • 16
DaoWen
  • 32,589
  • 6
  • 74
  • 101
  • 3
    I would also suggest to use `->>` macro for readability purpose. `(->> a (drop-last 3) (take 4))` – Ankur Aug 22 '12 at 09:31
  • 2
    @Alex Baranosky - Like I said, I don't think it helps much in the case where you're only applying two functions. If you're applying something more like 5 functions though I think using a threading macro makes the flow of control a lot more clear since you don't have to read inside-to-outside. These are just opinions though, not facts. – DaoWen Aug 23 '12 at 01:54
  • `(range 1 (inc 10))` and `(take 10 (next (range)))` are better alternatives to `(take 10 (iterate inc 1)))`, being closer to the python version in spirit or somewhat more legible IMHO. – omiel Jun 07 '14 at 19:34
26

Python's notion of a sequence is very different from Clojure's.

In Python,

  • a sequence is a finite ordered set indexed by non-negative numbers; and
  • a list is a mutable sequence which you can add slices to or remove slices from.

In Clojure,

  • a sequence is an interface supporting first, rest, and cons;
  • a list is an immutable sequential collection with cons (or rest) adding (or removing) first elements (returning lists so modified, anyway).

The nearest thing in Clojure to a Python list is a vector. As Adam Sznajder suggests, you can slice it using subvec, though you can't add or remove slices as you can in Python.

subvec is a fast constant-time operation, whereas drop makes you pay for the number of elements bypassed (take makes you pay for the elements you traverse, but these are the ones you are interested in).

Your examples become ...

(def a (vec (range 1 (inc 10))))

(subvec a 0 3)
; [1 2 3]

(subvec a 3)
; [4 5 6 7 8 9 10]

(subvec a 0 (- (count a) 3))
; [1 2 3 4 5 6 7]

(subvec a (- (count a) 3))
; [8 9 10]

(subvec a 3 (+ 3 4))
; [4 5 6 7]

(subvec a 3 (- (count a) 3))
; [4 5 6 7]
Community
  • 1
  • 1
Thumbnail
  • 13,293
  • 2
  • 29
  • 37
12

There is a function subvec. Unfortunately, it only works with vectors so you would have to transform your sequence:

http://clojuredocs.org/clojure_core/clojure.core/subvec

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Adam Sznajder
  • 9,108
  • 4
  • 39
  • 60
5

Slicing a sequence is a bit of a "code smell" - a sequence in general is designed for sequential access of items.

If you are going to do a lot of slicing / concatenation, there are much better data structures available, in particular checkout the RRB-Tree vector implementation:

This supports very efficient subvec and catvec operations.

mikera
  • 105,238
  • 25
  • 256
  • 415