Skip to content

Instantly share code, notes, and snippets.

@charliegriefer
Last active June 2, 2017 06:03
Show Gist options
  • Select an option

  • Save charliegriefer/1c8552ac9676ad247691cbec3bf6920c to your computer and use it in GitHub Desktop.

Select an option

Save charliegriefer/1c8552ac9676ad247691cbec3bf6920c to your computer and use it in GitHub Desktop.
The Elegance of Clojure
;; For a given list with N integers, return a new list removing the elements at odd positions.
;; Source: https://www.hackerrank.com/challenges/fp-filter-positions-in-a-list
;; My solution:
(fn[lst]
(map
#(when ((comp odd? first) %)
(println (second %)))
(map-indexed list lst)))
;; A _much_ more elegant solution:
(fn [lst]
(map second (partition 2 lst)))
@charliegriefer
Copy link
Author

charliegriefer commented Jun 1, 2017

For a given list with N integers, return a new list removing the elements at odd positions.

It seemed like an easy enough problem.

In an imperative language, I'd probably just loop over the list (collection), checking the loop index for each iteration. If the index is not odd, append the current list element to a new list, and ultimately return that list.

However, Clojure (and most functional languages) eschew such an approach. They've got their own built-in powerhouse functions for manipulating collections:

  • map
  • filter
  • reduce

For this problem, I knew I'd be using map. map takes a function, and applies it to each element in the provided collection.

But I also needed to know the position of the element in the list (or so I thought... more on that later). I checked the docs, and decided I'd use map-indexed.

By applying map-indexed to the collection, I'd get a collection of pairs, where the first value in each pair is the position in the list, and the second value is the list element itself.

So, given a lst of (4 8 15 17 23), the code on line 9 above returns the following:

user=> (map-indexed list lst)
;; => ((0 4) (1 8) (2 15) (3 17) (4 23))

Now it would be easy enough to map over that collection, and return the correct values.

map, as mentioned above, takes a function and applies it to each element in the collection.

I whipped up the following anonymous function:

(when ((comp odd? first) %) 
    (println (second %)))

The function will receive a pair (e.g. (1 8)), which is represented in the function as %.

I use when to check if the first element of the pair is odd?.

By using comp, I was able to compose a function that, in one swell foop, checked the first element of the pair to see if it was odd.

This sounds backwards, but as per the hackerrank page:

NOTE: By odd positions, we mean the first, third, fifth, etc position of the array needs to be filtered out. As per programming language conventions, these might (and they often do) correspond to indices and so on.

If that first element in the pair is odd, I print the second element of the pair out to the screen.

I hit "Run", and my function satisfied all of the test cases. Not too bad for a guy who hasn't touched Clojure in over a year.

But I knew that this wasn't the "best" solution. I remembered Clojure as being much more elegant than that. Having done my best, I ventured into the discussion area to see what other folks had come up with.

And there it was.

(fn [lst]
    (map second (partition 2 lst)))

And that is what I remember about Clojure. That, when done "right", it was like art.

This is where it pays to become intimately familiar with the built-in functions. partition hadn't even crossed my mind.

partition takes, at a minimum, a number n and a collection. It then groups the elements of that collection with n elements in each group.

For example:

user=> (partition 2 '(8 15 22 1 10 6 2 18 18 1))
;; => ((8 15) (22 1) (10 6) (2 18) (18 1))

With the resulting collection of pairs, we'd only want to return the second element of each pair. This effectively fulfills the requirements and gives us elements at odd positions.

Rather than having to pass map a complicated function like I did... in this case the only function map needs is the built-in second function.

It's not a noticeably smaller function than mine. two lines to three. But my function used:

  • map
  • when
  • comp
  • odd?
  • first
  • second
  • println
  • map-indexed
  • list

The more elegant function used:

  • map
  • second
  • partition

66% fewer function calls. I'd call that significant.

It's cleaner. It's much more elegant. And I think it very clearly demonstrates how, with a little bit of imagination, Clojure can shine.

@charliegriefer
Copy link
Author

charliegriefer commented Jun 1, 2017

It's worth pointing out that, for this particular problem, the elegant solution only works with an even number of elements in the original collection. With an odd number, and using partition with an n of 2, the last element of the original collection is dropped.

It's an easy enough fix:

(conj (vec lst) 0)

For example:

user=> (conj (vec '(1 2 3)) 0)
;; => [1 2 3 0]

This returns a new collection as a vector, but that's necessary, as conj'ing a new value to a list will place the new value at the beginning:

user=> (conj '(1 2 3) 0)
;; => (0 1 2 3)

The vector is treated the same as the list in the function in the gist.

Clojure does keep you on your toes :)

@charliegriefer
Copy link
Author

charliegriefer commented Jun 1, 2017

Just had a shower thought. Literally, came to me as I was shampooing my beard.

(fn [lst] 
    (rest (take-nth 2 (conj coll 0))))  



user=> (rest (take-nth 2 (conj '(8 15 22 1 10 6 2 18 18 1) 0)))
;; => (15 1 6 18 1)

I'll break it down later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment