Last active
June 2, 2017 06:03
-
-
Save charliegriefer/1c8552ac9676ad247691cbec3bf6920c to your computer and use it in GitHub Desktop.
The Elegance of Clojure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ;; 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))) |
Author
Author
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 :)
Author
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
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:
For this problem, I knew I'd be using map.
maptakes 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-indexedto the collection, I'd get a collection ofpairs, 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
lstof(4 8 15 17 23), the code on line9above returns the following:Now it would be easy enough to
mapover 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:
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:
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.
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.
partitionhadn't even crossed my mind.partition takes, at a minimum, a number
nand a collection. It then groups the elements of that collection withnelements in each group.For example:
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
mapa complicated function like I did... in this case the only functionmapneeds is the built-in second function.It's not a noticeably smaller function than mine. two lines to three. But my function used:
The more elegant function used:
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.