This page describes an operation in the collection pipeline pattern. For more context read:
- Collection Pipeline Article: An article explaining the collection pipeline pattern
- Operation Catalog: The list of selected operations that I have these pages for.
slice
Return a sub-sequence of the list between the given first and last positions.
If you want some of the list, you can take a slice of the list. Depending on what slice you want, there are different operators that languages provide.
The most general form allows you to slice out any range of elements.
[1,2,3,4,5,6].slice(2..4) # => [3, 4, 5]
(subvec [1 2 3 4 5 6] 2 5) ;; => [3 4 5]
These two basic examples raise a few points. Firstly most languages use zero based indexing, because programmers naturally count to ten by saying “0,1,2… 9”.
Ruby specifies the slice with an inclusive range, while clojure uses two numbers with the start included but the end excluded. Ruby's range notation allows the end to be excluded with:
ruby…[1,2,3,4,5,6].slice(2...5) # => [3, 4, 5]
I rarely use the triple-dot form, and some programmers dislike using it as the difference between “...” and “..” is very subtle.
Ruby can slice using array arguments.
ruby…[1,2,3,4,5,6][2..4] # => [3, 4, 5]
This form may not read so well in a pipeline form.
Clojure's subvec function will only work on vectors, not on
lists in general. It's fast on vectors (since they are indexed)
but would not be fast on other forms of clojure lists, so its
limitation to operating on vectors helps avoid performance
surprises at the loss of generality of the operation's usage.
Subvec also doesn't fit the convention of clojure's pipelines, in
that the collection argument isn't the last argument, so it won't
work with ->>
.
In clojure, you can omit the second argument to slice from the first to the end of the list, with ruby you do this by using negative numbers on the range..
[1,2,3,4,5,6].slice(2..-1) # => [3, 4, 5, 6]
(subvec [1 2 3 4 5 6] 2) ;; => [3 4 5 6]
Ruby's range can have negative arguments to count from the end of the list.
ruby…[1,2,3,4,5,6].slice(-4..-2) # => [3, 4, 5]
Instead of using a general slice operation, many languages
prefer separate take and drop functions. Take returns the first
elements (equivalent to slice(0..2)
in ruby).
[1,2,3,4,5,6].take(3) # => [1, 2, 3]
(take 3 [1 2 3 4 5 6]) ;; => (1 2 3)
Drop discards the first elements (slice(-3..-1)
in ruby).
[1,2,3,4,5,6].drop(3) # => [4, 5, 6]
(drop 3 [1 2 3 4 5 6]) ;; => (4 5 6)
You can form a slice by pipelining a take then a drop.
[1,2,3,4,5,6].take(5).drop(2) # => [3, 4, 5]
(drop 2 (take 5 [1 2 3 4 5 6])) ;; => (3 4 5)
Note that these numbers correspond to the exclusive range in ruby's slice operator.
The take-drop approach works well with lazy lists as both operations can be done without resolving the entire list.
If you need to work from the end of the list, clojure provides
take-last
and drop-last
.
(take-last 2 [1 2 3 4 5 6]) ;; => (5 6) (drop-last 2 [1 2 3 4 5 6]) ;; => (1 2 3 4)