Sudoku Solver
14.08.2013
Permalink
I'm not a
Sudoku
player. And even worse: before I went on my vacation last week, I
didn't even know the exact rules. With me, my wife and my daughter my
father-in-law Jürgen travelled with us. We had a nice flat on a farm
and enough time in the evenings, so Jürgen told me that he likes to
solve Sudokus. I suspected that Sudoku is one of those solitaire games
that could easily be solved by a deterministic algorithm, which is the
reason why I don't like playing these games. After all, it's at the
root of my profession to not do anything time-consuming using my hand
or my brain that a computer can do. Unless it's fun, of course.
So I asked him about the rules and how he approaches the solution. And
as we talked and had another glass of wine I thought about how an
implementation of a Sudoku solver would look like in Clojure. It
seemed not to be too much work... no, it seemed more like fun.
Three evenings later I finally finished a solver, you can find it as
Github repo.
In the
code
you can spot some interesting idioms that I consider very typical for
Clojure programs, and which contrast well how the FP approach differs
from an imperative OO approach:
Concepts backed by ordinary data structures
To write a clean implementation I needed some concepts:
a
cell with all its
candidate
values, a
fact denoting a cell that has a definite value,
a
region as abstract term for all 9 cells of a row, a column or
a 3x3 block, a
board comprised of 81 cells and so on. In an OOP
world I'd have created some classes and then had to decide where to
locate corresponding operations. And answer questions like "Is a board
a region, just a little bit bigger?" or "Is a fact a cell with a
special property?" to get inheritance and method reuse right.
In Clojure I still need concepts but I don't need specialised types to
express concepts. To make them a little more formal, factory functions
suffice that give the reader a clue what makes up a cell, a fact or a
board. In addition to conciseness this earns me the accessibility to
all those nice collection processing functions that work on Clojure
sequences.
Collection processing dominates
The intuitive way to represent a Sudoku board is a nested array that
models two dimensions. If Clojure did not have such a rich toolbox for
querying and transforming sequences the intuitive approach would be
fine. But, to take full advantage of what Clojure offers a board is
best represented as a sequence of maps, where each map represents a
cell. And hey, a region has the same representation, as well as facts
or possible guesses that can be derived from a board. That leads to
great uniformity and -as a nice consequence - to interoperability of
almost all functions.
In OOP the question of data representation is an encapsulated
implementation detail of a class. That looks like a nice idea first,
but forces you to implement similar collection processing
functionality over and over again. More code, more bugs, bad idea.
Predicates and queries
The core of the algorithm is a small set of transformation
functions. To enable a comprehensible implementation of these I
created several short functions that query cells or a board. They
create a conceptual layer that allows me to express the algorithm very
clearly on a higher level of abstraction. This is exactly one of the
central promises of OOP, and you can do the same in an OO language. In
OO the only caveat is that you must decide in which type to place
these helpers, and sometimes this question is hard to answer without
doubt. It's getting even harder when you need implementation reuse
because OOP only offers inheritance or delegation.
Conclusion
Yes, I can detect some recurring best practices across the few
libraries I wrote until now:
On top of collection processing and simple data structures I find it
helpful to describe
concepts up-front. A concept defines a name
and associates it with a data structure, invariants and
rules. Alternatively a concept could also be a specific kind of
function (see for example
Rings
handler or middleware) or even a protocol. A concept is only loosely
coupled to its implementation. Instances could occur everywhere
without referring to the initial code that introduced the concept
first. A concept is open, whereas an OO class or interface is somewhat
closed. Of course you can extend types in OOP but you need further
complexity like inheritance, factories, extensions, decorators or
other OO pattern stuff.
A safe way to create instances of a concept is a
factory function. I
quickly introduce those functions and prefix their names often with
verbs like "make-" or "create-". If I omit prefixes instead, then I
sometimes need to cripple local symbol names to avoid name clashes or
unwanted shadowing.
If I have a concept and its data structure then I often need
additional
query functionality to enable other functions to
work on a higher level of abstraction with my concepts. Among those,
predicates (functions that return true/false) play an important
role because some collection comprehensions like filter or remove
expect a predicate as parameter. Beside that most conditional
expressions really benefit from well-named predicates.
When I create functions that I expect to interoperate with each other
I always keep in mind how these functions can support the threading
macros -> and ->> as well as some higher-order functions like
partial. Especially
parameter order plays a much more important
role to support interoperability than in OOP.
After all: it was fun. And I really enjoy that after experimenting
with Clojure and FP now for a year, I feel like I'm gaining some sense
for idiomatic style that I can put into words and share with others.