Clojure Dictionary Challenge
There was a question today on Twitter about how to go about finding the word with the most consecutive consonants in the dictionary file. Of course, being a typical developer, when presented with a problem – I am usually not satisfied until I find a solution. Since I am interested in learning Clojure, I thought I would try to solve this problem functionally.
Armed with Stuart Halloway’s “Programming Clojure” and trusty Google by my side, I embarked on my first Clojure mission.
Starting small – I decided to tackle the issue of finding consecutive consonants in a word. I found that re-seq handles regex very nicely
user=> (re-seq #"[^aeiou]{2,}" "batty")
("tty")
Building on … let’s find the number of letters in this word. Since we have a sequence – we need to get the first element off of it, convert it to a string and find the length of it
user=> (.length (str (first (re-seq #"[^aeiou]{2,}" "batty"))))
3
Not bad – but let’s turn this into a function now
user=> (defn count-cons [s]
(.length (str (first (re-seq #"[^aeiou]{2,}" s))))
)
#'user/count-cons
Now we can call the function directly
user=> (count-cons "batty")
3
Alright – we are making progress. Next step, we need to be able to compare two words and see which has the bigger consonant count and return that. For this, we will need a little “if” magic.
user=> (defn compare-words [s1,s2]
(if (> (count-cons s1) (count-cons s2))
s1 s2))
#'user/compare-words
Let’s test it out
#'user/compare-words
user=> (compare-words "batty" "bat")
"batty"
user=> (compare-words "bat" "batty")
"batty"
Oh, yes, we are getting to the good part now. Since we have a function that we can call on two arguments and give us one back that we can work with – we are ready to pull out the big guns …. reduce!
user=> (reduce compare-words ["vat", "batty", "cars"])
"batty"
Yeah! Next we just need to feed it a file. For this, after doing some research, I used the duck-streams library – you need to load it in using
user=> (use '[clojure.contrib.duck-streams :only (spit read-lines)])
nil
We open the file and read the lines and feed it to our function
user=> (reduce compare-words (read-lines "/usr/share/dict/words"))
"Ångström"
Yes, my regex needs some tweaking - but for this exercise, I am content.
When you just have the working code. It is actually quite concise:
;Counting consecutive consonants
(use '[clojure.contrib.duck-streams :only (spit read-lines)])
(defn count-cons [s]
(.length (str (first (re-seq #"[^aeiou]{2,}" s))))
)
(defn compare-words [s1,s2]
(if (> (count-cons s1) (count-cons s2))
s1 s2))
(reduce compare-words (read-lines "/usr/share/dict/words"))
All in all, a fun evening trying to think functionally.