Getting the Penultimate Element in Clojure

clojure

I recently signed up for Code Wars in order to get more practice using Clojure - although I’ve really enjoyed doing Project Euler problems in Clojure, they eventually become more mathematics-focused than any sort of programming focused, and don’t provide much practice working with real world application datasets and the like.

The first kata that I completed is penultimate: define a function that returns the second to last element of a Clojure ISeq.

Here’s my gut reaction:

1
2
3
(defn penultimate [lst]
  "Returns the second to last element of an ISeq"
  (first (rest (reverse lst))))

Note that doing reverse breaks the laziness of lst, though this is necessary as there is no way to determine what the ‘second to last’ element of a list is without knowing that the list is in fact finite.

First, I could have cleaned up that parentheses nest using the -> threading operator:

1
2
(defn penultimate [lst]
  (-> lst reverse rest first))

Much prettier.

Here are the most interesting alternative implementations, and what I learned from them:

1
2
(def penultimate
  (comp second reverse))

The comp macro, which I was not previously familiar with, takes functions as their argument and returns their function composition as a new function. This is a very powerful macro - here, we sidestep having to explicitly defn a function at all, and just define penultimate to be the function that is the result of composing some functions! Beautiful. Thanks to all the code warriors who posted this solution (zoldar, importsoul, apage43, Odomontois, denisw).

Finally, several of the other solutions used butlast. butlast returns a sequence of all but the last item in a collection, allowing the following solution:

1
2
(defn penultimate [lst]
  (-> lst butlast last))

Or, equivalently,

1
2
(defn penultimate [lst]
  (last (butlast lst)))

Though not as pretty of a solution as the comp solution, this is still a really handy function to have in the toolbox - thanks to everyone who submitted answers using butlast!