droptake

This page describes an operation in the collection pipeline pattern. For more context read:

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.

ruby…
[1,2,3,4,5,6].slice(2..4)
# => [3, 4, 5]
clojure…
(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..

ruby…
[1,2,3,4,5,6].slice(2..-1)
# => [3, 4, 5, 6]
clojure…
(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).

ruby…
[1,2,3,4,5,6].take(3)
# => [1, 2, 3]
clojure…
(take 3 [1 2 3 4 5 6])
;; => (1 2 3)

Drop discards the first elements (slice(-3..-1) in ruby).

ruby…
[1,2,3,4,5,6].drop(3)
# => [4, 5, 6]
clojure…
(drop 3 [1 2 3 4 5 6])
;; => (4 5 6)

You can form a slice by pipelining a take then a drop.

ruby…
[1,2,3,4,5,6].take(5).drop(2)
# => [3, 4, 5]
clojure…
(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.

clojure…
(take-last 2 [1 2 3 4 5 6])
;; => (5 6)
(drop-last 2 [1 2 3 4 5 6])
;; => (1 2 3 4)