Python Challenge answers 0 thru 4... in clojure

The Python Challenge is a nifty site that presents you with a series of puzzles that it asks you to solve using python; getting each answer allows you to move on to the next puzzle.

Python is a cool language and it's a good tool for this job1 However, I'm learning clojure right now, so I thought it would be fun to try and solve a few of them in clojure. Here's my answers for challenges 0 thru 4 (warning: if you want to do these puzzles yourself, reading further now might ruin the fun)

Challenge #0 (the "Warmup")

Asks you to solve 2 to the 38th power:

(clojure.contrib.math/expt 2 38)

i.e. just use the exponent function in clojure contrib.

Challenge #1

This one throws some scrambled text at you and a clue on what the key is (ROT 2):

(defn translate [text]
  (let [lookup (vec (map char (range 97 123)))]
    (letfn [(letter? [c] (and (>= (int c) 97) (<= (int c) 122)))
            (shift-2 [c] (mod (+ 2 (- (int c) 97)) 26))]
      (apply str (map #(if (letter? %) (get lookup (shift-2 %)) %) text)))))

Create a lookup table of the chars, a predicate to test if a char is a letter. & a function to get the index of 2nd to next letter (the index loops, essentially making lookup as a ring buffer), then map across the given text, shifting by 2 if its a letter or just returning the char if its not.

Challenge #2

This one throws a big hunk of random data at you and suggests you pick out the 'rare' characters:

(defn filter-file [path]
  (let [fs (line-seq (clojure.contrib.io/reader path))
        lookup (set (map char (range 97 123)))]
    (apply str (mapcat #(filter lookup %) fs))))

A quick visual scan of the text led me to a strong hunch the "rare"2 characters were lowercase alpha, so:

Re-use our lookup table from the last challenge; this time make it a set, then use the set to filter each line of the file denoted by 'path' (I first saved the text to a file to make it easier to work with); use mapcat to flatten the lines out (this has the effect of stripping empty lines altogether); apply str to the resulting sequence to get the answer.

Challenge #3

This one's a big hunk of text too, so a quick refactoring of our last solution results in a more abstract (and higher-order) function that takes a filter function as an additional parameter:

(defn filter-file [filter-fn path]
    (apply str (mapcat filter-fn (line-seq (io/reader path)))))

the filter from challenge #2 thus becomes an argument; partial works nicely here:

(filter-file (partial filter (set (map char (range 97 123)))) "path/to/file")

Now we can make a new filter for challenge #3. This one will need to find character patterns that look like this: ABCxDEF. We'll need grab x. This one just screamed regex at me, so here's a filter that gives us the answer:

#(second (re-find #"[^A-Z][A-Z]{3}([a-z])[A-Z]{3}[^A-Z]" %)))

An anonymous function3 that uses re-find to match: "not-cap followed by 3 CAPS followed by not-cap followed by 3 CAPS followed by not-cap"; the second element of the resulting vector (because we use parens to create a group) produces x; mapcat et al do the rest.

Two big assumptions/limitations here: assumes each target is on its own line, and that the target pattern wasn't on the beginning or end of the line (which was good enough to get the answer).

Challenge #4

This challenge requires one to follow a url call chain, passing a different number as the argument to a 'nothing' parameter each time. The resulting page text provides the next number to follow (and/or some noise to keep you on your toes) until eventually we get the answer.

This one gets kinda ugly.

This is the kind of problem scripting languages are made for (e.g. perl, python & ruby coders would all make short work of this problem). Still, it's possible to write procedural code in clojure, and it's still reasonably straightforward.

One decision I had to make is how to GET the url's - my weapon of choice for this sort of thing is clj-http:

(require '[clj-http.client :as client])
(require '[clojure.contrib.string :as string]

(defn follow-chain [base-url number]
  (let [result (:body (client/get (str base-url number)))
        idx (.indexOf result "and the next")]
    (cond
      (re-find #"^Yes" result) (do
                                 (println result)
                                 (follow-chain base-url (/ (Integer/parseInt number) 2)))
      (= -1 idx)               result
      :else                    (let [result-vec (string/split (subs result idx) #" ")
                                     next-number (last result-vec)]
                                 (println result)
                                 (recur base-url next-number)))))

Take the url as a base & the first number to follow; use client-http/get to grab the page; extract the body of the page; get the index of the phrase "and the next" using the java "indexOf" method - we'll use the index later to parse out the end of the text and get the next number...

...unless of course, we get text that tells us something else (like a message saying "Yes" and then instructing us to divide the last number by two and continue on as before) so...

...we set up a switch using the cond macro: If the result starts with "Yes" make a recursive call dividing the last number by two; if indexOf otherwise came up empty, that's our answer, so return it; else pick the next number out of the result by splitting the end of the string into a vector (using clojure.contrib.string/split) and recur (tail recursively call the function again).

The println's could be removed, although they were essential when figuring out what the code needed to do.

Conclusion

This was a fun exercise; clojure's holding up pretty well so far, though clojure would not be my weapon of choice for that last one; if I choose to do the next five, I'll post them in a future article.

Footnotes

[1] It's also the darling of the hipster crowd right now -- in many cases the same people who snubbed python when ruby was the hip language about a decade ago... python abides.

[2] The official challenge answers also tackle ways to deduce "rare"; knock yourself out

[3] #() defines a function where % %2 etc represent positional parameters; the (fn [arg]) syntax would work here too