Clojure & Python, Side by Side

As an exercise, I translated Peter Norvig’s Sudoku solver from Python to Clojure. There have been a couple other attempts before, but they both leaned on Clojure’s reference types as a mutability-crutch. This implementation uses only immutable data structures.

To facilitate side-by-side comparison, I tried to retain the algorithm and basic code structure of Dr. Norvig’s implementation: variables, functions, and sections are named the same, etc. Some of the functions could be broken up into smaller pieces, but they’re not sinfully long.

Click here to see the Clojure and Python versions side-by-side.

What I didn’t do was focus on performance – beyond choosing what I thought were appropriate data structures up front. I wanted to bang it out using the tools that Clojure puts within close reach, and not worry about bleeding-edge speed.

Similarities

On the surface, the two versions are comparable in length and expressiveness. They both make extensive use of list comprehensions and their language’s particular idioms to help make things concise.

The algorithm remained intact for the most part. Dr. Norvig’s code didn’t rely too much on side effects so it wasn’t terribly difficult.

Differences

To avoid mutation, the Clojure version relies heavily on reduce to build a succession of game states. One challenge was that vanilla reduce wasn’t a perfect fit: the algorithms in assign and eliminate need to short circuit if a contradiction is encountered, which in turn allows the game-tree search to move on to better prospects. I resorted to creating reduce-true, which acts like regular reduce except that it stops feeding in values if any intermediate reductions produce a logically false value (which eliminate does when a contradiction is detected). Other alternatives crossed my mind (monads!?) but this seemed the most natural.

The eliminate and random-puzzle functions were the trickiest to translate. I’ve been practicing translating imperative algorithms into functional style a lot lately, and it’s getting easier. Thinking functionally forces you to become acutely aware of the values at play in your code — how and when one value gives rise to another, and what other values it depends on to do so.

The Clojure version didn’t have to worry about copying (see the search function), since everything is immutable. Immutability makes doing backtracking-style searches easy: no need to “undo” changes, since you never changed anything in the first place!

A bit to my surprise, the Python version is faster. This may be due to the choice of data structures: I chose to use vectors and sets to represent the Sudoku squares and digits, whereas the Python version uses strings, which are relatively lightweight. Changing it now would be too much work, but if anyone is curious enough to make the effort, speak up.

Just for kicks, I made a token attempt at parallelizing the algorithm by using pmap in search‘s first run (spawning between 2 and 4 threads usually). That didn’t help, probably because it causes extraneous work to be done when the solution is usually within close range in the game tree.

There are surely faster methods and algorithms possible. Again, my main goal was to practice thinking functionally, and compare the expressiveness of the resulting code to Python’s clean style. I think the Clojure code compares favorably in this light.

Stats

Clojure version:

Solved 50 of 50 easy puzzles (avg 0.01 secs (101 Hz), max 0.02 secs).
Solved 95 of 95 hard puzzles (avg 0.03 secs (33 Hz), max 0.16 secs).
Solved 11 of 11 hardest puzzles (avg 0.02 secs (64 Hz), max 0.04 secs).
Solved 99 of 99 random puzzles (avg 0.01 secs (97 Hz), max 0.02 secs).

Python version:

Solved 50 of 50 easy puzzles (avg 0.01 secs (126 Hz), max 0.01 secs).
Solved 95 of 95 hard puzzles (avg 0.03 secs (34 Hz), max 0.13 secs).
Solved 11 of 11 hardest puzzles (avg 0.01 secs (93 Hz), max 0.02 secs).
Solved 99 of 99 random puzzles (avg 0.01 secs (118 Hz), max 0.01 secs).

(The unpredictable spikes mentioned by Dr. Norvig persist in the Clojure version. Every once and a while, random-puzzle generates a puzzle that takes a very long time to solve. I didn’t make any effort to correct the problem.)

Advertisement
Posted in Clojure, Coding | 14 Comments

Launched YumTab

My side project, which uses Clojure and some basic machine learning to extract recipe information from websites, has launched: YumTab.

I’ll be updating the blog periodically with progress reports and possibly some juicy technical info.

Posted in Uncategorized | Leave a comment

Say something nice about every programming language you’ve used

Prompted by this blog post, an attempt to say something nice about every language I’ve used, roughly in the order that I learned them:

  • Pascal – My first love. Allowed me to create awesome text adventures, even though the code consisted entirely of spaghetti.
  • C – Gave me an appreciation for bit twiddling and memory poking, and opened the door to the demoscene and games with real graphics — like, with 256 colors.
  • C++ – A summer fling that I appreciated but never fully knew. Using << and >> for IO was neat.
  • x86 Assembly – Let me put pixels on the screen really fast, and made me feel smarter than a computer. INT 21h!
  • Perl – Gave me the power to bend any piece of text to my will in 3 lines or less.
  • PHP – Made web development easy and fun (security be damned). People actually wanted to pay me to do it, how cool is that?
  • Java – Abstraction is great! Objects are kind of neat.
  • JavaScript – Light-weight objects! First-class functions! Closures! Asynchronous processing! Prototypal inheritence! JavaScript gave me many lightbulb moments, thanks largely to Douglas Crockford.
  • Python – Providing one way to do it has benefits. List comprehensions are powerful.
  • Ruby – Code can make you chuckle. Rails corollary: CRUD apps don’t have to be tedious.
  • Clojure – So many insights:
    • Choosing the right abstractions makes all the difference.
    • Identity, value, and state should not be conflated.
    • OO is best served à la carte.
    • REPLs aid exploration and provide the groundwork for good tests.
    • When solving nontrivial problems, thinking should dominate typing.
    • Favor libraries over frameworks.
    • Macros are the icing, not the cake.
    • Functional programmers are well-balanced, friendly people.
    • Functional programming is really fun!

Haskell gets an honorable mention for being so beautiful, but most of my time with it has been spent reading, not writing. There are other languages I’ve dabbled in but they didn’t provide any particular insights.

Posted in Clojure, Coding | 1 Comment

Fun with Clojure: Turning Cats into Dogs in Hanoi

Finding a Connection

I’ve been having fun brushing up on basic graph theory lately. It’s amazing how many problems can be modeled with it. To that end, I did a code kata the other day that lent itself to a graph-based solution:

. . . the challenge is to build a chain of words, starting with one particular word and ending with another. Successive entries in the chain must all be real words, and each can differ from the previous word by just one letter.

One way to approach this is to think of all valid words as nodes in a graph, where words that differ from each other by one letter are connected. To find a path between one word, say “cat”, and another, “dog”, traverse the graph breadth-first starting at the “cat” node until you find the “dog” node.

Implementing this in Clojure is a cinch. First let’s create a dictionary of the words we’ll use:

(def dictionary
  (->> (slurp "/usr/share/dict/words")
       split-lines
       (map lower-case)
       (into #{})))

This takes in words from a file (OS X’s built-in dict here) and sticks them in a set. Having the words in a set gives us a fast and easy way to check whether a word is valid:

(filter dictionary ["cuspidor" "cromulent" "xebec"])
=> ("cuspidor" "xebec")

Next we need a function to give us a word’s neighbors:

(def alphabet "abcdefghijklmnopqrstuvwxyz")

(defn edits [^String word]
  "Returns words that differ from word by one letter. E.g.,
  cat => fat, cut, can, etc."
  (->> word
       (map-indexed (fn [i c]
                      (let [sb (StringBuilder. word)]
                        (for [altc alphabet :when (not= altc c)]
                          (str (doto sb (.setCharAt i altc)))))))
       (apply concat)
       (filter dictionary)))

For every letter in a word, replace it with every other letter in the alphabet; collect all these variations together and then keep only the legit ones.

Lastly, we need a function to actually perform the search:

(defn find-path [neighbors start end]
  "Return a path from start to end with the fewest hops (i.e. irrespective
  of edge weights), neighbors being a function that returns adjacent nodes"
  (loop [queue (conj clojure.lang.PersistentQueue/EMPTY start)
         preds {start nil}]
    (when-let [node (peek queue)]
      (let [nbrs (remove #(contains? preds %) (neighbors node))]
        (if (some #{end} nbrs)
          (reverse (cons end (take-while identity (iterate preds node))))
          (recur (into (pop queue) nbrs)
                 (reduce #(assoc %1 %2 node) preds nbrs)))))))

This is a fairly straight translation of the imperative algorithm.1 We use a PersistentQueue to keep track of nodes to visit next. The preds map does double-duty: it keeps track of nodes already seen, and allows us to trace our path back to the beginning once we reach our destination.

Now we’re ready to actually run the search:

(find-path edits "cat" "dog")
=> ("cat" "cot" "dot" "dog")
(find-path edits "four" "five")
=> ("four" "foud" "fold" "fole" "file" "five")
(find-path edits "bleak" "bloke")
=> ("bleak" "bleat" "blest" "blast" "blase" "blake" "bloke")

Nice. The longest of these runs in just over 100ms on my machine — not too shabby (though we can certainly do better). There are about 200k nodes and 100k edges in the word-chain graph.

Seeing it Through

I’m a visually-oriented person. Getting a correct result is well and good, but I want to see the process. To do that, I shaved an enormous yak and wrote a graph library for Clojure.

This new library helped me create pretty diagrams like the one at the top of this article. It outsources most of the hard work to the awesome GraphViz and the also-awesome Ubigraph tool, which lets you visualize graph structures and algorithms in realtime. Like this:

Expanding our Horizons

Another place graph traversal comes in handy is finding solutions to certain types of games, like Towers of Hanoi. Think of each possible position in the game as a node. Nodes connect to each other via valid moves.

So to solve a game, instead of an edits function, we need a moves function that takes a game state and returns valid neighboring states. Let’s solve Towers of Hanoi:

Towers of Hanoi, 3 disks 3 pegs

As our game state, we’ll use vector with an entry for each peg. Each peg will contain a sorted set of disks, in order of smallest to biggest. The game state where all disks are on the leftmost peg would look like this (assuming three disks and three pegs):

[#{0 1 2} #{} #{}]

Here’s the moves function:

(defn moves
  [state]
  (for [[from-peg disk] (map-indexed #(vector %1 (first %2)) state)
        to-peg (range (count state))
        :when (and disk
                   (not= from-peg to-peg)
                   (or (empty? (state to-peg))
                       (< disk (first (state to-peg)))))]
    (-> state
        (update-in [from-peg] disj disk)
        (update-in [to-peg] conj disk))))

For each topmost disk, see if we can move it to another peg. To do that, the other peg has to have a bigger top disk, or no disks at all.

Run the same find-path function on our new inputs…

(let [start [(sorted-set 1 2 3) (sorted-set) (sorted-set)]
      end [(sorted-set) (sorted-set) (sorted-set 1 2 3)]]
  (find-path moves start end))
=> ([#{1 2 3} #{} #{}] [#{2 3} #{} #{1}] [#{3} #{2} #{1}] [#{3} #{1 2} #{}] [#{} #{1 2} #{3}] [#{1} #{2} #{3}] [#{1} #{} #{2 3}] [#{} #{} #{1 2 3}])

And voilà. We have…something not so pretty. Loom, GraphViz, and Ubigraph to the rescue:

Here are renderings of solutions to Towers of Hanoi with three pegs and 3, 4, 5, 6, 7, and 8 disks (the last pushed the limits of GraphViz):

(Look familiar?)

Code

Click here for all the code used to create this post (plus some extra bits)

To Be Continued

Next time we’ll play with bigger graphs and leverage Clojure’s state-management tools to create parallel search algorithms.

Footnotes

  1. This version of find-path was used for blog simplicity. For a version that takes advantage of lazy sequences, see the gist for this post or the Loom source.

Posted in Clojure | 16 Comments