From d7c6c78f701f7f17a6c89dcdfd3f0a1b0a2714df Mon Sep 17 00:00:00 2001
From: Ray Miller
+ I'll take you on a 5-minutes tour of Clojure, but feel free to experiment on your own along the road!
+
+ You can type
+ The first thing you may notice about Clojure is that common operations look... strange.
+
+ For example, try typing
+ Hint: you can click on the code samples to insert them in the
+ REPL
+
+ That was a strange way to say "three plus three", wasn't it?
+
+ A Clojure program is made of lists.
+
+ Division might surprise you. When you're ready to move forward, try
+ Now, that was a bit surprising: Clojure has a built in Rational
+ type. You can still force Clojure to do floating point division
+ by making one of the operands floating point:
+ type Awesome!
+ Many Clojure functions can take an arbitrary number of arguments.
+ Try it out: type
+ That's enough math. Let's do some fun stuff, like defining functions.
+ You can do that in Clojure with
+ Type Congratulations - you just defined your first Clojure function. Many more will follow!
+
+ Oh, sorry for talking so long - you probably want to try out your brand new function!
+ Type Yay! It works!
+ By now, you probably think that Clojure is very different from
+ the programming languages you already know. Indeed, it belongs to a
+ different family than most popular languages' - the family of
+ "functional" programming languages. Like most functional languages,
+ Clojure can define a function without even giving it a name:
+
+ If you run this code, you'll see some cryptic output.
+ In Clojure, functions are just normal values like numbers or strings.
+
+ But wait - an anonymous function isn't very useful if you can't
+ call it. Try to define a new anonymous function and call it
+ straight
+ away:
+ Let's see what you just did: you evaluated a list where the
+ first element is the function itself, defined on the spot - and the
+ other elements are the arguments that you pass to the function.
+ That's exactly the same syntax that you used earlier on to call
+ functions like
+ Remember
+ If you want, you can create a named functions without
+ using
+ Success! Now you can call this new
+ By now, you know that lists are quite important in Clojure.
+ But Clojure also has other data structures:
+
+ Vectors:
+ Vectors and lists are sequential and ordered collections.
+ Sets are not ordered, and they cannot contain duplicate elements.
+ Maps are key-value collections, where the keys can be any object.
+ Here, we've used what Clojure calls a keyword (
+ Now I'll tell you another thing that may surprise you: Clojure
+ collections are immutable - they can never change. When
+ you do anything on a list, including adding and removing
+ elements, you actually get a brand new list. (Fortunately,
+ Clojure is amazingly efficient at creating new lists). In
+ general, Clojure encourages you to have as little mutable state
+ as possible. For example, instead qof "for" loops and other
+ state-changing constructs, most of the time you'll see functions
+ doing transformations on immutable data and returning new
+ collections, without changing the old one.
+
+ A prime example of this is Great job!
+ I told you that Clojure's data structures are immutable. Let's take
+ a closer look at what that means.
+
+ When you used the
+ Now try
+ We can use the
+ Now examine
+ Type
+ Glad to know you're eager for more, but unfortunately this tutorial is over.
+
+ Maybe you can click the 'links' button above for more resources?
+
- Glad to know you're eager for more, but unfortunately this tutorial is over.
-
- Maybe you can click the 'links' button above for more resources?
-
- I'll take you on a 5-minutes tour of Clojure, but feel free to experiment on your own along the road!
-
- You can type
- Success! Now you can call this new
- By now, you know that lists are quite important in Clojure.
- But Clojure also has other data structures:
-
- Vectors:
- Vectors and lists are sequential and ordered collections.
- Sets are not ordered, and they cannot contain duplicate elements.
- Maps are key-value collections, where the keys can be any object.
- Here, we've used what Clojure calls a keyword (
- Now I'll tell you another thing that may surprise you: Clojure collections are immutable - they can never change.
- When you do anything on a list, including adding and removing elements, you actually get a brand new list.
- (Fortunately, Clojure is amazingly efficient at creating new lists).
- In general, Clojure encourages you to have as little mutable state as possible.
- For example, instead of "for" loops and other state-changing constructs, most of the time you'll see functions doing transformations on immutable data and returning new collections, without changing the old one.
-
- A prime example of this is Great job!
- We've only scratched the surface of Clojure and its mind-bending power.
- This tutorial is still a work in progress, and I'm working on more steps.
- Meanwhile, you can learn more about Clojure by visiting the 'links' page.
-
- Welcome to your adventures in Clojure, and be prepared to be surprised and delighted every step of the way!
-
- The first thing you may notice about Clojure is that common operations look... strange.
-
- For example, try typing
- That was a strange way to say "three plus three", wasn't it?
-
- A Clojure program is made of lists.
-
- Division might surprise you. When you're ready to move forward, try
- Now, that was a bit surprising: Clojure has a built in Rational type.
- You can still force Clojure to do floating point division by making one of the operands floating point: type Awesome!
- Many Clojure functions can take an arbitrary number of arguments.
- Try it out: type
- That's enough math. Let's do some fun stuff, like defining functions.
- You can do that in Clojure with
- Type Congratulations - you just defined your first Clojure function. Many more will follow!
-
- Oh, sorry for talking so long - you probably want to try out your brand new function!
- Type Yay! It works!
- By now, you probably think that Clojure is very different from the programming languages you already know.
- Indeed, it belongs to a different family than most popular languages' - the family of "functional" programming languages.
- Like most functional languages, Clojure can define a function without even giving it a name:
-
- If you run this code, you'll see some cryptic output.
- In Clojure, functions are just normal values like numbers or strings.
-
- But wait - an anonymous function isn't very useful if you can't call it. Try to define a new anonymous function and call it straight away:
- Let's see what you just did: you evaluated a list where the first element is the function itself, defined on the spot - and the other elements are the arguments that you pass to the function.
- That's exactly the same syntax that you used earlier on to call functions like
- Remember
- If you want, you can create a named functions without using next
to skip forward, back
to return to the previous step, and restart
to get back to the beginning. Let's get started: type next
.
+ (+ 3 3)
in the REPL.
+ (+ 3 3)
is a list that contains an operator, and then the operands.
+ Try out the same concept with the *
and -
operators.
+ (/ 10 3)
.
+ (/ 10 3.0)
to continue.
+ (+ 1 2 3 4 5 6)
to continue.
+ defn
.
+ (defn square [x] (* x x))
to define a "square" function that takes a single number and squares it.
+ defn
takes the name of the function, then the list of arguments, and then the body of the function.
+ I told you that a Clojure program is made of lists, right?
+ The entire defn
is a list, and the function body is also a list.
+ (Even the arguments are collected in a vector, which is similar to a list - we'll talk about vectors soon).
+ (square 10)
.
+ (fn [x] (* x x))
+ fn
defines a function and then returns it.
+ What you're seeing is simply what a function looks like when you print it on the screen.
+ ((fn [x] (* x x)) 10)
.
+ square
or even +
. The only
+ difference is that now you defined the function in the same place
+ where you called it.
+ defn
? Now I can tell you a
+ secret: defn
is actually just a bit of syntactic
+ sugar around def
and fn
. You've just
+ seen fn
at work: it defines a new function.
+ def
binds the newly defined function to a name.
+ defn
: type
+ (def square (fn [x] (* x x)))
to continue.
+ square
function
+ just like you called the old square
function.
+ [1 2 3 4]
+ Maps: {:foo "bar" 3 4}
+ Sets: #{1 2 3 4}
+ :foo
) for one of the keys, and a number for the other key.
+ map
. map
is
+ a higher order function, which means that it takes
+ another function as an argument. For example, you can
+ ask map
to increment each number in a vector by
+ passing it the inc
function, followed by the
+ vector. Try it for yourself: type
+ (map inc [1 2 3 4])
to continue.
+ map
function above to transform the
+ vector [1 2 3 4]
, the original vector was unchanged.
+ Let's give our vector a name: type
+ (def v [1 2 3 4])
.
+ Entering v
at the REPL
+ prints the value of v
to the console.
+ (map inc v)
, then
+ type v
again - it remains unchanged!
+ conj
function to add elements to a
+ vector: try (conj v 5)
.
+ v
- it is still the vector
+ [1 2 3 4]
. Clojure has returned a new vector
+ with the value 5
added no the end. Clojure uses
+ structural sharing to manage these immutable data
+ structures in a memory-efficient manner.
+ next
to continue.
+ next
to skip forward, back
to return to the previous step, and restart
to get back to the beginning. Let's get started: type next
.
-square
function just like you called the old square
function.
-[1 2 3 4]
- Maps: {:foo "bar" 3 4}
- Sets: #{1 2 3 4}
-:foo
) for one of the keys, and a number for the other key.
-map
. map
is a higher order function, which means that it takes another function as an argument.
- For example, you can ask map
to increment each number in a vector by passing it the inc
function, followed by the vector.
- Try it for yourself: type (map inc [1 2 3 4])
to continue.
-(+ 3 3)
in the REPL.
-(+ 3 3)
is a list that contains an operator, and then the operands.
- Try out the same concept with the *
and -
operators.
-(/ 10 3)
.
-(/ 10 3.0)
to continue.
-(+ 1 2 3 4 5 6)
to continue.
-defn
.
-(defn square [x] (* x x))
to define a "square" function that takes a single number and squares it.
-defn
takes the name of the function, then the list of arguments, and then the body of the function.
- I told you that a Clojure program is made of lists, right?
- The entire defn
is a list, and the function body is also a list.
- (Even the arguments are collected in a vector, which is similar to a list - we'll talk about vectors soon).
-(square 10)
.
-(fn [x] (* x x))
-
-fn
defines a function and then returns it.
- What you're seeing is simply what a function looks like when you print it on the screen.
-((fn [x] (* x x)) 10)
.
-square
or even +
.
- The only difference is that now you defined the function in the same place where you called it.
-defn
?
- Now I can tell you a secret: defn
is actually just a bit of syntactic sugar around def
and fn
.
- You've just seen fn
at work: it defines a new function.
- def
binds the newly defined function to a name.
-defn
: type (def square (fn [x] (* x x)))
to continue.
-
- You can type next
to skip forward, back
to return to the previous step, and restart
to get back to the beginning. Let's get started: type next
.
+ You can type next
to skip
+ forward, back
to return to the previous step,
+ and restart
to get back to the beginning. Let's get
+ started: type next
.
next
to continue.
+ Let's put some of the things we have learned to work. We're + going to write a function to solve Sudoku puzzles. (If + you're not familiar with Sudoku, you might want to + + read about it before coming back here.) +
+
+ Each cell in the Sudoku grid can contain any of the digits 1-9.
+ We can use the range
function to generate a list of
+ integers. Try it out: (range 10)
.
+
+ That generated the list of numbers from 0-9, which might
+ surprise you. When called with a single argument, (range end)
,
+ it returns a list of integers from 0 up to (but not
+ including) end
.
+
+ With two arguments,
+ (range start end)
, it returns the
+ numbers from start
up to (but not
+ including) end
. An optional third argument
+ specifies the step size; try
+ (range 1 50 5)
.
+
+ Experiment with the range
function and, when you're
+ ready to continue, generate the list 1-9 we need for our Sudoku
+ grid.
+
+ That was easy! +
+
+ It will be convenient to store the candidates for each cell as a
+ set. We can use the set
function to turn the
+ sequence generated by range
into a set. Type
+ (def candidates (set (range 1 10)))
to
+ continue.
+
+ We have the set of candidates for each cell, but we also need to + decide how to represent the 9x9 grid. The simplest data + structure we can use is an 81-element vector, with the first + element (index 0) representing the top left of the grid (column + 0, row 0), and the 81st element (index 80) the bottom right + (column 8, row 8). Note that we are using coordinates counting + from 0 - this makes some of the arithmetic we have to do later a + bit simpler. +
+
+ With this in mind, can you complete this function definition?
+ (defn cell-index [col row] ...)
.
+ Test your function by computing the index of column 7, row 2.
+
+ Did your function return the value 25? If not, evaluate the
+ following code to define cell-index
:
+
(defn cell-index [col row] (+ col (* 9 row)))
.
+ + We will represent our (unsolved) Sudoku grid as a vector of candidates for + each cell. Let's define the empty grid, where the values of all cells are + unknown: +
+(def empty-grid (vec (repeat 81 candidates)))
.
+
+ Like we used set
to turn the sequence generated
+ by range
into a set, here we are
+ using vec
to turn the sequence generated
+ by repeat
into a vector.
+
+ To continue, try combining the core function get
+ with your cell-index
function to retrieve the
+ candidates for column 3, row 4 from the empty grid.
+
+ Well done! +
+
+ Now, if we're going to solve Sudoku puzzles, we need to read a string
+ representation of the puzzle into our grid of candidates. If we
+ use 0
to represent an unknown value, we can
+ represent the puzzle like so:
+
+
+ (def puzzle "530070000
+ 600195000
+ 098000060
+ 800060003
+ 400803001
+ 700020006
+ 060000280
+ 000419005
+ 000080079")
+
+
+ + We need to transform this into a sequence of integers that we can + use to initialize our grid. Let's use a regular expression to + pick out the digits: +
+
+ (re-seq #"\d" puzzle)
+
+
+ That's close to what we're looking for, we just need to convert
+ these strings into numbers. Java's Integer
class
+ has a static method for doing just that, and as we're runnnig
+ Clojure on the JVM, we can simply use Clojure's Java
+ interoperability support. Define the following:
+
+ (defn parse-int [s] (Integer/parseInt s))
+
+ + With that in hand, try: +
+
+ (map parse-int (re-seq #"\d" puzzle))
+
+ + You might be thinking that we need to loop over this list of + integers to build our grid. Well, we could, but in + Clojure explicit looping is very rare - we usually reach for a + higher-order function. +
+
+ Let's define one more helper function. This function will take a
+ digit d
and return the set of candidates for the
+ cell. If d
is zero, this will just be the
+ set candidates
we defined earlier. Otherwise, it
+ will be the set containing the single element d
.
+ The set
function we used earlier expects a list as
+ its first argument, so we could wrap d
in a vector
+ and write:
+ (set [d])
, but we prefer to use its
+ sister function, hash-set
:
+
+
+ (defn candidates-for [d]
+ (if (zero? d) candidates (hash-set d)))
+
+
+
+ Try it out for a few values of d
, and
+ type next
when you're ready to
+ continue.
+
+ With this helper function in hand, we can use map
+ to build our grid. Try:
+
+
+ (def grid (map candidates-for
+ (map parse-int (re-seq #"\d" puzzle))))
+
.
+
+
+ When you see two invocations of map
together like
+ this, you might be thinking there is a better way to express
+ the solution, and you'd be right! We can use Clojure's comp
function to compose
+ the two functions:
+
+
+ (def grid (map (comp candidates-for parse-int)
+ (re-seq #"\d" puzzle)))
+
+
+
+ We're almost there! Let's look at the data type of grid
:
+
+ (type grid)
+
+ + We have generated a list (actually a lazy + sequence, but we can think of it as a list). Lists and + vectors in Clojure have different properties, one of those differences + being that vectors support fast indexed access while indexed + access to a list takes linear time. This won't make much difference + to performance when our list is only 81 elements long, but let's not get + into bad habits when we're just starting out. +
+
+ We could wrap the output in a call to vec
, like we did to
+ create an empty grid, but returning a vector from map
is a common
+ enough occurrence that Clojure provides a function for just this purpose, namely mapv
.
+ Let's put everything together into a function that parses a puzzle and returns the candidates
+ grid as a vector:
+
+
+ (defn parse-puzzle [puzzle]
+ (mapv (comp candidates-for parse-int)
+ (re-seq #"\d" puzzle)))
+
+
+ + New we can define our grid: +
+(def grid (parse-puzzle puzzle))
+ + What are the candidates for the cell at column 4, row 5? +
++ Before we go on to solve the puzzle, let's write a function to + pretty-print our grid. We'd like the output to look like: +
++ +---+---+---+ + |53.|.7.|...| + |6..|195|...| + |.98|...|..6| + +---+---+---+ + |8..|.6.|..3| + |4..|8.3|..1| + |7..|.2.|..6| + +---+---+---+ + |.6.|...|28.| + |...|419|..5| + |...|.8.|.79| + +---+---+---+ ++
+ First, let's write a function to render a cell. If the cell is a number, or a set of + candidates with only one element, we should render the number, otherwise a dot. +
+
+
+ (defn render-cell [x]
+ (cond
+ (number? x) x
+ (= (count x) 1) (first x)
+ :else "."))
+
+
+
+ This is the first time we've seen cond
, which is
+ similar to a switch
statement in other languages.
+ There's nothing special about the :else
keyword in
+ the last branch - any truthy value would do.
+
+ Type true
to continue.
+
+ Let's write a function to pull out the rendered values for a
+ given row. You'll see we're using the function for
below. This
+ does not introduce a loop like for
does in
+ some other languages. In Clojure, for
is
+ a list
+ comprehension. This is a very powerful construct, and we'll see
+ more of it later.
+
+
+ (defn row-render-values [grid row]
+ (for [col (range 9) :let [cell (get grid (cell-index col row))]]
+ (render-cell cell)))
+
+
+
+ Try it: (row-render-values grid 4)
.
+
+ Now, we need to partition these values into groups of 3 and
+ interpose pipe symbols. Luckily, Clojure comes with partiiton
+ and interpose
functions!
+ Try (partition 3 (range 9))
and
+ (interpose "|" (partition 3 (range 9)))
.
+
+ Of course, we need a pipe at the beginning and the end of each row too:
+ ["|" (interpose "|" (partition 3 (range 9))) "|"]
+
+ We've got all the characters we need to render in the right + order, but unfortunately we've ended up with a nested data + structure, not a string we can print. Once again, Clojure's core + library has a function that comes to our rescue: +
+
+
+ (flatten ["|" (interpose "|" (partition 3 (range 9))) "|"])
+
+
+
+ We need one more trick to render this row. If we try to print
+ the list returned by flatten
, we won't get quite
+ the right output. Try (str ["A" "ba" "cus"])
+ and compare that output with (str "A" "ba" "cus")
.
+ See the difference? The str
function expects to be called
+ with multiple arguments, not a single (list) argument. To get
+ the results we desire, we have to apply
+ the str
function to the list returned
+ by flatten
, making it behave as if we'd
+ called str
with multiple arguments:
+
+
+ (apply str (flatten ["|" (interpose "|" (partition 3 (range 9))) "|"]))
+
+
+ + With this in hand, we can render a grid row! +
+
+
+ (defn render-grid-row [grid row]
+ (apply str (flatten ["|" (interpose "|" (partition 3 (row-render-values grid row))) "|"])))
+
+
+
+ Try it: (render-grid-row grid 4)
.
+
+ We're going to have to call (render-grid-row grid row
+ for values of row
ranging from 0 to 8.
+ Hopefully by now you'll realise that this is a prime candidate
+ for map
. We could write:
+
+
+ (map (fn [row] (render-grid-row grid row)) (range 9))
+
+
+
+ ...and, while that would be perfectly fine, we could also use (partial render-grid-row grid)
+ create a new function that is just like render-grid-row
, but with the first argument pre-populated.
+
+
+ (map (partial render-grid-row grid) (range 9))
+
+
+ + When it comes to rendering the grid, we need to print a horizontal separator at the start + and end, and between every 3 rows. +
+
+
+ (def hsep "+---+---+---+")
+
+
+
+ We can use flatten
, interpose
and partition
+ to build the grid, like we did for each row:
+
+
+ (flatten [hsep (interpose hsep (partition 3 (map (partial render-grid-row grid) (range 9)))) hsep])
+
+
+
+ Let's put all this together to build our render-grid
function.
+
+
+ (defn render-grid [grid]
+ (let [hsep "+---+---+---+"
+ rows (map (partial render-grid-row grid) (range 9))]
+ (doseq [line (flatten [hsep (interpose hsep (partition 3 rows)) hsep])]
+ (println line))))
+
+
+
+ To continue, type (render-grid grid)
.
+
+ We're going to solve the puzzle using a combination + of assignment, elimination and search + (more on this later). When we assign the value 6 to the + cell (0,1), no other cells in the same row, column or 3x3 square + can have the value 6 - so we eliminate it from the + corresponding candidate lists. +
+
+ We call the cells we have to eliminate when assigning a value to
+ a cell its peers, and we're going to need a function to
+ return the list of peers of a given cell. We'll build
+ the peers
function from three helper
+ functions, row-peers
, col-peers
+ and square-peers
.
+
+ Let's define row-peers
:
+
+ (defn row-peers [col row] (for [x (range 9)] [x row]))
+
+
+
+ col-peers
is almost the same:
+
+ (defn col-peers [col row] (for [y (range 9)] [col y]))
+
+
+ + We have to work a little bit harder to calculate the minor square for a cell. We calculate the + row and column of the top-left corner, and return the cells up to 3 columns to the right and 3 rows down. +
+
+ (defn square-peers [col row]
+ (let [top-left-col (* 3 (quot col 3))
+ top-left-row (* 3 (quot row 3))]
+ (for [delta-row (range 3) delta-col (range 3)]
+ [(+ top-left-col delta-col) (+ top-left-row delta-row)])))
+
+
+
+ Try it out: (square-peers 4 5)
.
+
Glad to know you're eager for more, but unfortunately this tutorial is over.
- Maybe you can click the 'links' button above for more resources? + Maybe you can click the 'links' button below for more resources?
fn
defines a function and then returns it.
What you're seeing is simply what a function looks like when you print it on the screen.
-
+
But wait - an anonymous function isn't very useful if you can't call it. Try to define a new anonymous function and call it @@ -115,7 +115,8 @@
If you want, you can create a named functions without
using defn
: type
- (def square (fn [x] (* x x)))
to continue.
+
(def square (fn [x] (* x x)))
+ to continue.
Now try (map inc v)
, then
type v
again - it remains unchanged!
-
We can use the conj
function to add elements to a
vector: try (conj v 5)
.
-
Now examine v
- it is still the vector
[1 2 3 4]
. Clojure has returned a new vector
@@ -262,13 +263,13 @@
Did your function return the value 25? If not, evaluate the
following code to define cell-index
:
(defn cell-index [col row] (+ col (* 9 row)))
.
+ (defn cell-index [col row] (+ col (* 9 row)))
We will represent our (unsolved) Sudoku grid as a vector of candidates for each cell. Let's define the empty grid, where the values of all cells are unknown:
-(def empty-grid (vec (repeat 81 candidates)))
.
+ (def empty-grid (vec (repeat 81 candidates)))
Like we used set
to turn the sequence generated
by range
into a set, here we are
@@ -291,19 +292,15 @@
use 0
to represent an unknown value, we can
represent the puzzle like so:
-
- (def puzzle "530070000
- 600195000
- 098000060
- 800060003
- 400803001
- 700020006
- 060000280
- 000419005
- 000080079")
-
-
+ (def puzzle "530070000
+ 600195000
+ 098000060
+ 800060003
+ 400803001
+ 700020006
+ 060000280
+ 000419005
+ 000080079")
We need to transform this into a sequence of integers that we can
use to initialize our grid. Let's use a regular expression to
@@ -350,12 +347,8 @@
(set [d])
, but we prefer to use its
sister function, hash-set
:
-
- (defn candidates-for [d]
- (if (zero? d) candidates (hash-set d)))
-
-
+ (defn candidates-for [d]
+ (if (zero? d) candidates (hash-set d)))
Try it out for a few values of d
, and
type next
when you're ready to
@@ -367,24 +360,16 @@
With this helper function in hand, we can use map
to build our grid. Try:
-
- (def grid (map candidates-for
- (map parse-int (re-seq #"\d" puzzle))))
-
.
-
+ (def grid (map candidates-for
+ (map parse-int (re-seq #"\d" puzzle))))
When you see two invocations of map
together like
this, you might be thinking there is a better way to express
the solution, and you'd be right! We can use Clojure's comp
function to compose
the two functions:
-
- (def grid (map (comp candidates-for parse-int)
- (re-seq #"\d" puzzle)))
-
-
+ (def grid (map (comp candidates-for parse-int)
+ (re-seq #"\d" puzzle)))
@@ -535,12 +520,10 @@
With this in hand, we can render a grid row!
-
-
- (defn render-grid-row [grid row]
- (apply str (flatten ["|" (interpose "|" (partition 3 (row-render-values grid row))) "|"])))
-
-
+ (defn render-grid-row [grid row]
+ (apply str
+ (flatten
+ ["|" (interpose "|" (partition 3 (row-render-values grid row))) "|"])))
Try it: (render-grid-row grid 4)
.
row-peers
:
(defn row-peers [col row] (for [x (range 9)] [x row]))
-
+
col-peers
is almost the same:
@@ -646,7 +629,7 @@
Try it out: (square-peers 4 5)
.
Glad to know you're eager for more, but unfortunately this tutorial is over. @@ -656,4 +639,3 @@
If you want, you can create a named functions without
- using defn
: type
-
(def square (fn [x] (* x x)))
+ using defn
; type
+
+
+ (def square (fn [x] (* x x)))
+
+
to continue.
@@ -241,35 +245,141 @@
continue.
- We have the set of candidates for each cell, but we also need to - decide how to represent the 9x9 grid. The simplest data - structure we can use is an 81-element vector, with the first - element (index 0) representing the top left of the grid (column - 0, row 0), and the 81st element (index 80) the bottom right - (column 8, row 8). Note that we are using coordinates counting - from 0 - this makes some of the arithmetic we have to do later a - bit simpler. + decide how to represent the 9x9 grid: +
++ A1 A2 A3 | A4 A5 A6 | A7 A8 A9 + B1 B2 B3 | B4 B5 B6 | B7 B8 B9 + C1 C2 C3 | C4 C5 C6 | C7 C8 C9 + ---------+----------+--------- + D1 D2 D3 | D4 D5 D6 | D7 D8 D9 + E1 E2 E3 | E4 E5 E6 | E7 E8 E9 + F1 F2 F3 | F4 F5 F6 | F7 F8 F9 + ---------+----------+--------- + G1 G2 G3 | G4 G5 G6 | G7 G8 G9 + H1 H2 H3 | H4 H5 H6 | H7 H8 H9 + I1 I2 I3 | I4 I5 I6 | I7 I8 I9 ++
+ The simplest data structure we can use is an 81-element vector, + with the first element (index 0) holding A1, the second element + (index 1) holding A2, etc. until finally the 81st element (index + 80) stores I9.
- With this in mind, can you complete this function definition?
- (defn cell-index [col row] ...)
.
- Test your function by computing the index of column 7, row 2.
+ We're going to need a function to compute the index of a given
+ cell in our vector representation of the grid; let's start by
+ converting our alphabetic row indices into numeric indices. Clojure's
+ strings are actually Java strings, so we can take the string
+ "ABCDEGFHI"
and use the java.lang.String/indexOf
+ method to find the index.
+
+ Calling Java methods from Clojure is really easy: just prefix the
+ Java method with .
and call it as a Clojure function with
+ the object as the first argument. Try it now:
+ (.indexOf "ABCDEFGHI" "C")
.
+
+ That returned a zero-indexed row, which is just what we need. We can do a similar
+ thing for the column: (.indexOf "123456789" "3")
. We're working
+ towards a function that we can call like (cell-index "A3")
, so the final piece
+ of the puzzle is to split "A3"
into its row and column components "A"
+ and "3"
. There are many ways to do this, but here's one to try:
+ (map str "A3")
.
+ Now, we could write +
+
+ (let [row (first (map str "A3"))] ...)
+
+ + and +
+
+ (let [col (second (map str "A3"))] ...)
+
+ + but Clojure provides a very powerful mechanism called destructuring + that helps us out here. (Clojure's destructuring is worthy of a + tutorial in its own right; Jay Fields has written + a nice introduction.) +
++ Using destructuring, we can simply write +
+
+ (let [[row col] (map str "A3") ...)
+
+
+ (note the extra set of square brackets inside the let
). Putting this
+ all together, we have:
+
+
+ (let [[row col] (map str "A3")
+ row-index (.indexOf "ABCDEFGHI" row)
+ col-index (.indexOf "123456789" col)]
+ [row-index col-index])
+
+
+
+ Try running that code fragment, and type next
to continue.
+
+ We now have all the tools we need to write our cell-index
function. Can you
+ complete this function definition?
+
+
+ (defn cell-index
+ [cell]
+ (let [[row col] (map str cell)
+ row-index (.indexOf "ABCDEFGHI" row)
+ col-index (.indexOf "123456789" col)]
+ ...))
+
+
+ + Test your function by computing the index of C8. +
+
Did your function return the value 25? If not, evaluate the
following code to define cell-index
:
(defn cell-index [col row] (+ col (* 9 row)))
+
+
+ (defn cell-index
+ [cell]
+ (let [[row col] (map str cell)
+ row-index (.indexOf "ABCDEFGHI" row)
+ col-index (.indexOf "123456789" col)]
+ (+ col-index (* 9 row-index))))
+
+
We will represent our (unsolved) Sudoku grid as a vector of candidates for each cell. Let's define the empty grid, where the values of all cells are unknown:
-(def empty-grid (vec (repeat 81 candidates)))
+
+
+ (def empty-grid (vec (repeat 81 candidates)))
+
+
Like we used set
to turn the sequence generated
by range
into a set, here we are
@@ -279,7 +389,7 @@
To continue, try combining the core function get
with your cell-index
function to retrieve the
- candidates for column 3, row 4 from the empty grid.
+ candidates for cell "E4" from the empty grid.
0
to represent an unknown value, we can
represent the puzzle like so:
- (def puzzle "530070000
- 600195000
- 098000060
- 800060003
- 400803001
- 700020006
- 060000280
- 000419005
- 000080079")
+
+
+ (def puzzle "530070000
+ 600195000
+ 098000060
+ 800060003
+ 400803001
+ 700020006
+ 060000280
+ 000419005
+ 000080079")
+
+
We need to transform this into a sequence of integers that we can use to initialize our grid. Let's use a regular expression to pick out the digits:
-
- (re-seq #"\d" puzzle)
-
+
+
+ (re-seq #"\d" puzzle)
+
+
That's close to what we're looking for, we just need to convert
these strings into numbers. Java's Integer
class
- has a static method for doing just that, and as we're runnnig
- Clojure on the JVM, we can simply use Clojure's Java
- interoperability support. Define the following:
+ has a static method for doing just that, so we can again use
+ Clojure's Java interoperability support. Define the following:
- (defn parse-int [s] (Integer/parseInt s))
-
+
+
+ (defn parse-int [s] (Integer/parseInt s))
+
+
+ (Note the different syntax for calling a static method.) With that in hand, try:
-
- (map parse-int (re-seq #"\d" puzzle))
-
+
+
+ (map parse-int (re-seq #"\d" puzzle))
+
+
@@ -344,11 +464,15 @@
The set
function we used earlier expects a list as
its first argument, so we could wrap d
in a vector
and write:
- (set [d])
, but we prefer to use its
+ (set [d])
, but we prefer to use its
sister function, hash-set
:
(defn candidates-for [d]
- (if (zero? d) candidates (hash-set d)))
+
+
+ (defn candidates-for [d]
+ (if (zero? d) candidates (hash-set d)))
+
+
Try it out for a few values of d
, and
type next
when you're ready to
@@ -360,18 +484,24 @@
With this helper function in hand, we can use map
to build our grid. Try:
(def grid (map candidates-for
- (map parse-int (re-seq #"\d" puzzle))))
+
+
+ (def grid (map candidates-for (map parse-int (re-seq #"\d" puzzle))))
+
+
When you see two invocations of map
together like
this, you might be thinking there is a better way to express
the solution, and you'd be right! We can use Clojure's comp
function to compose
the two functions:
(def grid (map (comp candidates-for parse-int)
- (re-seq #"\d" puzzle)))
+
+
+ (def grid (map (comp candidates-for parse-int) (re-seq #"\d" puzzle)))
+
+
We're almost there! Let's look at the data type of grid
:
+(defn parse-puzzle [puzzle] (mapv (comp candidates-for parse-int) @@ -406,7 +536,7 @@
(def grid (parse-puzzle puzzle))
- What are the candidates for the cell at column 4, row 5? + What are the candidates for the cell C8?
- First, let's write a function to render a cell. If the cell is a number, or a set of + We'll start by writing a function to render a cell. If the cell is a number, or a set of candidates with only one element, we should render the number, otherwise a dot.
-+(defn render-cell [x] (cond @@ -452,39 +582,53 @@ Type
true
to continue.
Let's write a function to pull out the rendered values for a
given row. You'll see we're using the function for
below. This
does not introduce a loop like for
does in
some other languages. In Clojure, for
is
- a list
+ a list
comprehension. This is a very powerful construct, and we'll see
more of it later.
+(defn row-render-values [grid row] - (for [col (range 9) :let [cell (get grid (cell-index col row))]] - (render-cell cell))) + (for [col (range 1 10) :let [cell (str row col)]] + (render-cell (get grid (cell-index cell)))))
- Try it:
(row-render-values grid 4)
. + Try it:(row-render-values grid "D")
.
Now, we need to partition these values into groups of 3 and
interpose pipe symbols. Luckily, Clojure comes with partiiton
- and interpose
functions!
- Try (partition 3 (range 9))
and
- (interpose "|" (partition 3 (range 9)))
.
+ and interpose
functions! Try:
+
+
+ (partition 3 (range 9))
+
+
+ and
+
+
+ (interpose "|" (partition 3 (range 9)))
+
+
Of course, we need a pipe at the beginning and the end of each row too:
- ["|" (interpose "|" (partition 3 (range 9))) "|"]
+
+ ["|" (interpose "|" (partition 3 (range 9))) "|"]
+
+
@@ -493,7 +637,7 @@ structure, not a string we can print. Once again, Clojure's core library has a function that comes to our rescue:
-+(flatten ["|" (interpose "|" (partition 3 (range 9))) "|"])
@@ -501,8 +645,18 @@We need one more trick to render this row. If we try to print the list returned by
+flatten
, we won't get quite - the right output. Try(str ["A" "ba" "cus"])
- and compare that output with(str "A" "ba" "cus")
. + the right output. Try: +++(str ["A" "ba" "cus"])
++ and compare that output with +
+++(str "A" "ba" "cus")
+See the difference? The
-str
function expects to be called with multiple arguments, not a single (list) argument. To get the results we desire, we have toapply
@@ -510,52 +664,58 @@ byflatten
, making it behave as if we'd calledstr
with multiple arguments:+(apply str (flatten ["|" (interpose "|" (partition 3 (range 9))) "|"]))
With this in hand, we can render a grid row!
-(defn render-grid-row [grid row]
- (apply str
- (flatten
- ["|" (interpose "|" (partition 3 (row-render-values grid row))) "|"])))
+
+
+ (defn render-grid-row [grid row]
+ (apply str
+ (flatten
+ ["|" (interpose "|" (partition 3 (row-render-values grid row))) "|"])))
+
+
- Try it: (render-grid-row grid 4)
.
+ Try it: (render-grid-row grid "D")
.
We're going to have to call (render-grid-row grid row
- for values of row
ranging from 0 to 8.
+ for values of row
ranging from "A" to "I".
Hopefully by now you'll realise that this is a prime candidate
for map
. We could write:
+- (map (fn [row] (render-grid-row grid row)) (range 9)) + (map (fn [row] (render-grid-row grid row)) "ABCDEFGHI")
- ...and, while that would be perfectly fine, we could also use
-(partial render-grid-row grid)
- create a new function that is just likerender-grid-row
, but with the first argument pre-populated. + ...and, while that would be perfectly fine, we could also use +(partial render-grid-row grid)
+ create a new function that is just likerender-grid-row
, + but with the first argument pre-populated.+- (map (partial render-grid-row grid) (range 9)) + (map (partial render-grid-row grid) "ABCDEFGHI")
When it comes to rendering the grid, we need to print a horizontal separator at the start and end, and between every 3 rows.
-+(def hsep "+---+---+---+")
@@ -564,19 +724,22 @@ We can useflatten
,interpose
andpartition
to build the grid, like we did for each row: -+- (flatten [hsep (interpose hsep (partition 3 (map (partial render-grid-row grid) (range 9)))) hsep]) + (flatten [hsep + (interpose hsep + (partition 3 (map (partial render-grid-row grid) "ABCDEFGHI"))) + hsep])
Let's put all this together to build our
-render-grid
function.+(defn render-grid [grid] (let [hsep "+---+---+---+" - rows (map (partial render-grid-row grid) (range 9))] + rows (map (partial render-grid-row grid) "ABCDEFGHI")] (doseq [line (flatten [hsep (interpose hsep (partition 3 rows)) hsep])] (println line))))
@@ -617,7 +780,7 @@We have to work a little bit harder to calculate the minor square for a cell. We calculate the row and column of the top-left corner, and return the cells up to 3 columns to the right and 3 rows down. -
+(defn square-peers [col row] (let [top-left-col (* 3 (quot col 3)) diff --git a/src/tryclojure/views/tutorial.clj b/src/tryclojure/views/tutorial.clj index 7fd21d6..b498f03 100644 --- a/src/tryclojure/views/tutorial.clj +++ b/src/tryclojure/views/tutorial.clj @@ -1,5 +1,6 @@ (ns tryclojure.views.tutorial (:require [tryclojure.models.tutorial :as m] + [clojure.string :as str] [net.cgrand.enlive-html :as html] [noir.response :as response])) @@ -9,10 +10,38 @@ (Integer/parseInt n) (catch NumberFormatException _))) +(defn count-leading-whitespace + [s] + (count (take-while #(Character/isWhitespace %) s))) + +(defn trim-leading-whitespace + [n] + (fn [s] + (subs s (min n (count-leading-whitespace s))))) + +(defn clean-whitespace + [s] + (let [lines (remove str/blank? (str/split-lines s)) + n (count-leading-whitespace (first lines))] + (str/join "\n" (map (trim-leading-whitespace n) lines)))) + +(defn strip-blank-lines + [s] + (if (and (string? s) (str/blank? s)) + nil + s)) + +(defn format-code-blocks + [node] + (first + (html/at node + [:pre.codeblock] (html/transform-content strip-blank-lines) + [:pre.codeblock :code] (html/transform-content clean-whitespace)))) + (defn tutorial-html [page] (when-let [n (Integer/parseInt page)] (when-let [content (m/get-page n)] - (apply str (html/emit* (html/unwrap content)))))) + (apply str (html/emit* (html/unwrap (format-code-blocks content))))))) (defn tutorial-meta [] (when-let [metadata (m/get-metadata)] From 00ffe5b06d493898dc983cc31dace378839c90dd Mon Sep 17 00:00:00 2001 From: Ray Miller
Date: Tue, 8 Jul 2014 19:37:57 +0100 Subject: [PATCH 07/37] More content. --- resources/public/tutorial.html | 145 +++++++++++++++++++++++++-------- 1 file changed, 113 insertions(+), 32 deletions(-) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index b391d93..eb92f25 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -297,7 +297,16 @@
- Now, we could write
+ There is something subtle going on here. When used as a sequence,
+ the string "A3"
behaves like a list of characters; try
+ (seq "A3")
in the REPL.
+
+ Clojure knows that the map
function expects a
+ sequence as its second argument, so it automatically coerces the
+ string to a sequence of characters for us.
+
+ With this in hand, we could write
(let [row (first (map str "A3"))] ...)
@@ -333,7 +342,7 @@
- Try running that code fragment, and type next
to continue.
+ Try running that code fragment, and type next
to continue.
- (Note the different syntax for calling a static method.)
+ (Note the different syntax for calling a static method: the class name,
+ followed by a /
, then the method name.)
With that in hand, try:
@@ -448,7 +458,7 @@
You might be thinking that we need to loop over this list of
integers to build our grid. Well, we could, but in
@@ -461,12 +471,27 @@
cell. If d
is zero, this will just be the
set candidates
we defined earlier. Otherwise, it
will be the set containing the single element d
.
- The set
function we used earlier expects a list as
- its first argument, so we could wrap d
in a vector
- and write:
- (set [d])
, but we prefer to use its
- sister function, hash-set
:
+ How are we going to create a set containing just one element? Try:
+ (set 3)
.
+
+ The error message is telling us that
+ the set
function expects a list as its first argument, but we
+ are passing the value 3
(a Long).
+
+ We could wrap our single argument in a vector, and call set
on that; Try:
+ (set [3])
.
+
+ Clojure also provides the hash-set
function which takes zero or more arguments
+ and returns a set of those arguments; try:
+ (hash-set 3)
.
+
(defn candidates-for [d]
@@ -700,7 +725,7 @@
...and, while that would be perfectly fine, we could also use
- (partial render-grid-row grid)
+ (partial render-grid-row grid)
to
create a new function that is just like render-grid-row
,
but with the first argument pre-populated.
(render-grid grid)
.
We're going to solve the puzzle using a combination of assignment, elimination and search (more on this later). When we assign the value 6 to the - cell (0,1), no other cells in the same row, column or 3x3 square + cell B1, no other cells in the same row, column or 3x3 square can have the value 6 - so we eliminate it from the corresponding candidate lists.
@@ -767,31 +793,86 @@
Let's define row-peers
:
-
- (defn row-peers [col row] (for [x (range 9)] [x row]))
-
+
+
+
+ (defn row-peers [cell]
+ (let [[row _] (map str cell)]
+ (for [col "123456789"] (str row col))))
+
+
+
+ We're using destructuring again to extract the row from the cell; you'll
+ often see the name _
used in Clojure to indicate a variable whose
+ value we aren't going to use - here, we don't need the column of the input cell,
+ as we're going to range across all columns. Try it out:
+ (row-peers "D5")
.
col-peers
is almost the same:
-
- (defn col-peers [col row] (for [y (range 9)] [col y]))
-
+
+
+ (defn col-peers [cell]
+ (let [[_ col] (map str cell)]
+ (for [row "ABCDEFGHI"] (str row col))))
+
+
- We have to work a little bit harder to calculate the minor square for a cell. We calculate the - row and column of the top-left corner, and return the cells up to 3 columns to the right and 3 rows down. -
-
- (defn square-peers [col row]
- (let [top-left-col (* 3 (quot col 3))
- top-left-row (* 3 (quot row 3))]
- (for [delta-row (range 3) delta-col (range 3)]
- [(+ top-left-col delta-col) (+ top-left-row delta-row)])))
-
-
-
- Try it out: (square-peers 4 5)
.
-
(col-peers "D5")
.
+
+ + We have to work a little bit harder to calculate the minor square for a cell. We + could do some arithmetic to work this out, but we'll keep things simple and just + hard-code a lookup. +
+
+
+ (defn square-peers [cell]
+ (let [[row col] (map str cell)
+ rows (case row
+ ("A" "B" "C") "ABC"
+ ("D" "E" "F") "DEF"
+ ("G" "H" "I") "GHI")
+ cols (case col
+ ("1" "2" "3") "123"
+ ("4" "5" "6") "456"
+ ("7" "8" "9") "789")]
+ (for [row rows col cols] (str row col))))
+
+
+
+ case
is similar to cond
, which we saw earlier;
+ type (doc case)
to learn more.
+
+ Try it out: (square-peers "D5")
.
+
+ We need to return all the peers for a given cell, which we do by concatenating the + results of the three simpler functions; try: +
+
+
+ (concat (square-peers "D5") (row-peers "D5") (col-peers "D5"))
+
+
+ + You might see some duplicates in this list, but these are easily dealt with: +
+
+
+ (defn peers [cell]
+ (distinct (concat (square-peers cell) (row-peers cell) (col-peers cell))))
+
+
+
+ Try it out: (peers "D5")
.
+
From a295c531465acd7494a01d4e63f5396089ff954b Mon Sep 17 00:00:00 2001
From: Ray Miller
That's close to what we're looking for, we just need to convert
- these strings into numbers. Java's
- (Note the different syntax for calling a static method: the class name,
- followed by a
+ We can now define the
- Try it out for a few values of
We're almost there! Let's look at the data type of
We have generated a list (actually a lazy
sequence, but we can think of it as a list). Lists and
@@ -557,12 +573,30 @@
- New we can define our grid:
+ Now we can define our grid:
- What are the candidates for the cell C8?
+ We can look up an element in a vector using
+ Clojure vectors also act as functions that take an index as their
+ first argument and return the value at that index; try it now:
+
@@ -851,7 +885,7 @@
Try it out:
We need to return all the peers for a given cell, which we do by concatenating the
results of the three simpler functions; try:
@@ -867,19 +901,566 @@
Try it out:
+ Finally, a cell is not a peer of itself, so we have to remove it from the list.
+ Here's one way to do it:
+
+ Here, we've written a predicate function that will
+ return
+ What are the peers of cell C8?
+
+ We mentioned earlier that Clojure's
+ The
+ We can also accumulate counters or lists in a loop; for example, to sum the numbers from
+
+ Try it out:
+ We can also use
+ Try it out:
+ Although
+ We pass
+ The accumulator function takes the current value of the accumulator as its first
+ argument and the next value to accumulate as its second argument, and returns the new
+ value of the accumulator. We used
+ The initial value for the accumulator is optional, so we can simplify this function further:
+
+ This works because
+ OK, back to our Sudoku solver. With
+ We'll start with a simple helper function to eliminate a value
+ from a single cell:
+
+ Each grid cell contains a set of candidates
+ for that cell. When we've "solved" a cell, we'll store the
+ solution as a single value (an integer). If this integer is the value
+ we're trying to eliminate, we have reached an inconsistent state. We'll
+ indicate this by returning
+ When the cell value is a set of candidates, we remove
+ Putting this all together, we have:
+
+ Recall our original puzzle:
+ It worked! When we try to eliminate 5 as a possible value for A1,
+ we get an empty set, and our
+ We can now write our
+ We know that cell A1 has the value 5, so we can try eliminating the value 5 from the
+ other cells in row A:
+
- Glad to know you're eager for more, but unfortunately this tutorial is over.
+ So far, so good. But what happens if A1 also appears in the list of cells for
+ elimination?
- Maybe you can click the 'links' button below for more resources?
+ Hmm. In this case,
+ Wrapping the
+ Great! That's
+ The
+ We're seeing
+ We're also seeing
+ Let's try out the
+ And an illegal assignment:
+
+ We can't assign 5 to A2, as that is the only candidate for A1 so
+ would introduce an inconsistency. In this case, our
+ The
+ Of course,
+ This should return
+ We also need to find the first singleton in the grid. By singleton, We
+ mean a set with one element:
+
+ ...and here's how to find the first singleton in the grid:
+
+ What is the first singleton in our grid?
+
+ Now we can make a start on a function for solving Sudoku puzzles!
+
+ This next command may take a few seconds to run...
+
+ Yay, it worked! This simple process of assignment and elimination solved this puzzle.
+ But we're not done yet... Let's define a harder puzzle:
+
+ Try solving this grid:
+ This time, we didn't manage to solve the grid with our simplistic
+ approach (although we did solve a couple of cells). This is
+ where search comes in. We find the first cell with
+ multiple candidates, and try the first candidate. If this results
+ in an inconsistent grid, we backtrack and try the next value for
+ that cell.
+
+ This is where Clojure's immutable data structures really show their strength:
+ backtracking comes for free, as trying a value creates a new grid without mutating
+ the original one! Let's take a look at our
+ When we can't find a singleton cell in the grid, we hit the else branch
+ on line 9, giving up and returning the unsolved grid. This is where we need
+ to focus our attention. Type
+ Let's start by writing a function to find the first unsolved cell. This is similar to
+
+ Try it out:
+ We can now add search to our
+ Phew! There's quite a lot going on in there. We use
+ Let's try it (but be patient, it may take a few seconds):
+
+ That's all folks!
+
+ We hope you enjoyed this whirlwind tour of Clojure. The Sudoku problem was inspired
+ by an article by Peter Norvig
+ - check it out if you'd like to see his Python implementation.
+
+ We've only scratched the surface of Clojure. If you're keen to know more,
+ click on the 'links' button above for suggested further reading.
+
- That's enough math. Let's do some fun stuff, like defining functions.
+ That's enough arithmetic. Let's do some fun stuff, like defining functions.
You can do that in Clojure with
@@ -188,7 +188,7 @@
vector: try
- Now examine
- If you want, you can create a named functions without
+ If you want, you can create a named function without
using
Before we go on to solve the puzzle, let's write a function to
- pretty-print our grid. We'd like the output to look like:
+ pretty-print our grid. We'd like the output to look like this:
- Try solving this grid:
This is where Clojure's immutable data structures really show their strength:
backtracking comes for free, as trying a value creates a new grid without mutating
- the original one! Let's take a look at our Welcome to Clojure! You can see a Clojure interpreter above - we call it a REPL. Type
I'll take you on a 5-minutes tour of Clojure, but feel free to experiment on your own along the road!
diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj
index ee2b239..383041c 100644
--- a/src/tryclojure/views/home.clj
+++ b/src/tryclojure/views/home.clj
@@ -60,6 +60,7 @@
[:div#container
[:div#console.console]
[:div#buttons
+ [:a#tutorial.buttons "tutorial"]
[:a#links.buttons "links"]
[:a#about.buttons.last "about"]]
[:div#changer (home-html)]]
From d3ccf3c7dc20a2cdeb6845c230b4c5c0cb2391a9 Mon Sep 17 00:00:00 2001
From: Tom Crinson
+ Let's use
+ The first argument of
@@ -665,7 +681,7 @@
Now, we need to partition these values into groups of 3 and
- interpose pipe symbols. Luckily, Clojure comes with
- We now have all the tools we need to write our
Putting this all together, we have:
+ We're seeing
This next command may take a few seconds to run...
Let's write a function to pull out the rendered values for a
given row. You'll see we're using the function
- The first argument of
+ Type
+ The
+ The
+ We want to look up in our grid all the values for a given row.
+ We can use
+ This leads us to the following function, which takes the row we
+ are interested in as its second argument:
From 58b542f7bf83e6547d9fd503deaed94b9eec50b8 Mon Sep 17 00:00:00 2001
From: Ray Miller
(note the extra set of square brackets inside the
If you run this code, you'll see some cryptic output.
In Clojure, functions are just normal values like numbers or strings.
@@ -705,7 +707,7 @@
:when (even? sum)]
sum)
-
+
We want to look up in our grid all the values for a given row.
We can use
- I'll take you on a 5-minutes tour of Clojure, but feel free to experiment on your own along the road!
+ I'll take you on a brief tour of Clojure, but feel free to
+ experiment on your own along the road!
+
+ A quick note on conventions. When you see something like
+
You can type
+ Let's get started: type
A Clojure program is made of lists.
-
@@ -86,7 +99,7 @@
"functional" programming languages. Like most functional languages,
Clojure can define a function without even giving it a name:
@@ -102,7 +115,7 @@
away:
Let's see what you just did: you evaluated a list where the
first element is the function itself, defined on the spot - and the
@@ -122,9 +135,9 @@
If you want, you can create a named function without
using
- Success! Now you can call this new
By now, you know that lists are quite important in Clojure.
@@ -172,8 +186,77 @@
+ Let's take a closer look at some of Clojure's data structures,
+ starting with maps:
+
+ We can use the
+ Maps also act as functions - give a key as an argument, they
+ return the value for that key in the map:
+
+ One of reasons we use keywords as keys in maps is that they also
+ act as functions that look themselves up in a map!
+
+ What happens if we try to look up a key that is not present in
+ the map?
+
+ We get back
+ We can also use
+ What happens if we try to
+ ...and if the element is not there?
+ Great job!
I told you that Clojure's data structures are immutable. Let's take
a closer look at what that means.
@@ -222,15 +305,15 @@
That generated the list of numbers from 0-9, which might
- surprise you. When called with a single argument,
With two arguments,
-
It will be convenient to store the candidates for each cell as a
set. We can use the
+ You'll notice that sets are unordered.
+ Let's give our candidates a name; enter
That returned a zero-indexed row, which is just what we need. We can do a similar
thing for the column:
- There is something subtle going on here. When used as a sequence,
- the string
Clojure knows that the
+ We have seen
+ Outside of the
With this in hand, we could write
- (note the extra set of square brackets inside the
+ Type
+ Putting this all together, we have:
+
+ (You can use shift-ENTER to enter multiple lines into
+ the console, and you can use the up and down arrows to scroll
+ through the console history.)
+
Test your function by computing the index of C8.
With that in hand, try:
We can now define the
Try it out for a few values of
We're almost there! Let's look at the data type of
Now we can define our grid:
We can look up an element in a vector using
Let's use
The
+ For completeness, I should also tell you
+ about
We want to look up in our grid all the values for a given row.
We can use and
Of course, we need a pipe at the beginning and the end of each row too:
and compare that output with
@@ -795,7 +933,7 @@
by
With this in hand, we can render a grid row!
Let's put all this together to build our
- To continue, type
Let's define
You might see some duplicates in this list, but these are easily dealt with:
The initial value for the accumulator is optional, so we can simplify this function further:
Putting this all together, we have:
Let's try out the
And an illegal assignment:
This should return
...and here's how to find the first singleton in the grid:
Now we can make a start on a function for solving Sudoku puzzles!
This next command may take a few seconds to run...
We can now add search to our
Let's try it (but be patient, it may take a few seconds):
Welcome to Clojure! You can see a Clojure interpreter above - we call it a REPL. Type
I'll take you on a 5-minutes tour of Clojure, but feel free to experiment on your own along the road!
diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj
index 62c582d..9b9a7ca 100644
--- a/src/tryclojure/views/home.clj
+++ b/src/tryclojure/views/home.clj
@@ -61,6 +61,7 @@
[:div#console.console]
[:div#changer (home-html)]
[:div#buttons
+ [:a#tutorial.buttons "tutorial"]
[:a#links.buttons "links"]
[:a#about.buttons.last "about"]]]
[:div.footer
From 59892e7e4144eca6eabf7e5de35c4e35bf367b6a Mon Sep 17 00:00:00 2001
From: Ray Miller
Let's write a function to pull out the rendered values for a
given row. You'll see we're using the function
- The first argument of
+ Type
+ The
+ The
+ We want to look up in our grid all the values for a given row.
+ We can use
+ This leads us to the following function, which takes the row we
+ are interested in as its second argument:
From 6b71f19a7613f541715e044df0bb36e65f399508 Mon Sep 17 00:00:00 2001
From: Ray Miller
(note the extra set of square brackets inside the
If you run this code, you'll see some cryptic output.
In Clojure, functions are just normal values like numbers or strings.
@@ -705,7 +707,7 @@
:when (even? sum)]
sum)
-
+
We want to look up in our grid all the values for a given row.
We can use
- I'll take you on a 5-minutes tour of Clojure, but feel free to experiment on your own along the road!
+ I'll take you on a brief tour of Clojure, but feel free to
+ experiment on your own along the road!
+
+ A quick note on conventions. When you see something like
+
You can type
+ Let's get started: type
A Clojure program is made of lists.
-
@@ -86,7 +99,7 @@
"functional" programming languages. Like most functional languages,
Clojure can define a function without even giving it a name:
@@ -102,7 +115,7 @@
away:
Let's see what you just did: you evaluated a list where the
first element is the function itself, defined on the spot - and the
@@ -122,9 +135,9 @@
If you want, you can create a named function without
using
- Success! Now you can call this new
By now, you know that lists are quite important in Clojure.
@@ -172,8 +186,77 @@
+ Let's take a closer look at some of Clojure's data structures,
+ starting with maps:
+
+ We can use the
+ Maps also act as functions - give a key as an argument, they
+ return the value for that key in the map:
+
+ One of reasons we use keywords as keys in maps is that they also
+ act as functions that look themselves up in a map!
+
+ What happens if we try to look up a key that is not present in
+ the map?
+
+ We get back
+ We can also use
+ What happens if we try to
+ ...and if the element is not there?
+ Great job!
I told you that Clojure's data structures are immutable. Let's take
a closer look at what that means.
@@ -222,15 +305,15 @@
That generated the list of numbers from 0-9, which might
- surprise you. When called with a single argument,
With two arguments,
-
It will be convenient to store the candidates for each cell as a
set. We can use the
+ You'll notice that sets are unordered.
+ Let's give our candidates a name; enter
That returned a zero-indexed row, which is just what we need. We can do a similar
thing for the column:
- There is something subtle going on here. When used as a sequence,
- the string
Clojure knows that the
+ We have seen
+ Outside of the
With this in hand, we could write
- (note the extra set of square brackets inside the
+ Type
+ Putting this all together, we have:
+
+ (You can use shift-ENTER to enter multiple lines into
+ the console, and you can use the up and down arrows to scroll
+ through the console history.)
+
Test your function by computing the index of C8.
With that in hand, try:
We can now define the
Try it out for a few values of
We're almost there! Let's look at the data type of
Now we can define our grid:
We can look up an element in a vector using
Let's use
The
+ For completeness, I should also tell you
+ about
We want to look up in our grid all the values for a given row.
We can use and
Of course, we need a pipe at the beginning and the end of each row too:
and compare that output with
@@ -795,7 +933,7 @@
by
With this in hand, we can render a grid row!
Let's put all this together to build our
- To continue, type
Let's define
You might see some duplicates in this list, but these are easily dealt with:
The initial value for the accumulator is optional, so we can simplify this function further:
Putting this all together, we have:
Let's try out the
And an illegal assignment:
This should return
...and here's how to find the first singleton in the grid:
Now we can make a start on a function for solving Sudoku puzzles!
This next command may take a few seconds to run...
We can now add search to our
Let's try it (but be patient, it may take a few seconds):
Integer
class
- has a static method for doing just that, so we can again use
- Clojure's Java interoperability support. Define the following:
+ these strings into numbers. Java's Integer
class has
+ a static method for doing just that, so we can again use Clojure's
+ Java interoperability support. We saw earlier that we can call
+ methods on objects by adding a .
to the front of the
+ method name and calling it as a Clojure function. The syntax for
+ calling a static method is slightly different:
+ (ClassName/methodName arg ...)
Using this, we can
+ define:
@@ -448,8 +453,6 @@
/
, then the method name.)
With that in hand, try:
@@ -491,7 +494,10 @@
(hash-set 3)
.
candidates-for
function:
+
(defn candidates-for [d]
@@ -499,9 +505,17 @@
d
, and
- type next
when you're ready to
- continue.
+ Try it out for a few values of d
:
+
+
+
+ (candidates-for 3)
+
+
+
+ (candidates-for 0)
+
grid
:
- (type grid)
-
+
+
+ (type grid)
+
+ (def grid (parse-puzzle puzzle))
+
+
+ (def grid (parse-puzzle puzzle))
+
+ get
:
+
+
+ (get grid (cell-index "D5"))
+
+
+
+ (grid (cell-index "D5"))
+
+ (square-peers "D5")
.
(defn peers [cell]
- (distinct (concat (square-peers cell) (row-peers cell) (col-peers cell))))
+ (distinct (concat (square-peers cell)
+ (row-peers cell)
+ (col-peers cell))))
(peers "D5")
.
+
+
+ (defn peers [cell]
+ (remove (fn [x] (= x cell))
+ (distinct (concat (square-peers cell)
+ (row-peers cell)
+ (col-peers cell)))))
+
+ true
when (= x cell)
. It is more idiomatic in Clojure
+ to use a singleton set as a predicate function in these circumstances. Sets behave like functions
+ that return the element, if the set contains the element, otherwise nil
. Experiment with
+ some sets: (#{"a" "b" "c"} "b")
or (#{"a" "b" "c"} "d")
.
+ This allows us to write:
+
+
+
+ (defn peers [cell]
+ (remove #{cell} (distinct (concat (square-peers cell)
+ (row-peers cell)
+ (col-peers cell)))))
+
+ for
function does
+ not introduce a loop. So how do we loop in Clojure? Like
+ many functional programming languages, we loop by using
+ recursion. Here's a simple example of recursion using
+ Clojure's loop
and recur
:
+
+
+
+ (loop [i 0]
+ (println (str "Iteration " i))
+ (if (> i 3)
+ (println "Done")
+ (recur (inc i))))
+
+ loop
function takes a binding form just like let
+ and initializes our loop variables. Here the variable i
is keeping track
+ of the number of iterations. The if
statement checks a termination condition
+ that stops us from looping forever. When i <= 3
, we increment i
+ and recur
.
+ 1
to n
, we could write:
+
+
+
+ (defn triangle-number [n]
+ (loop [accum 0 n n]
+ (if (> n 0)
+ (recur (+ accum n) (dec n))
+ accum)))
+
+ (triangle-number 6)
.
+ loop
/recur
to write a
+ more general sum
function to add up an arbitrary list of
+ numbers:
+
+
+
+ (defn sum [xs]
+ (loop [accum 0 xs xs]
+ (if (empty? xs)
+ accum
+ (recur (+ accum (first xs)) (rest xs)))))
+
+ (sum [7 8 9])
. Summing the
+ numbers from 1..n
should be equivalent to our triangle-number
function:
+ (= (triangle-number 7) (sum (range 1 8)))
(remember that range
+ includes the lower-bound but excludes the upper-bound, hence the 8
here).
+ loop
/recur
is the primary looping construct in Clojure, it is
+ rarely used in practice. Whenever possible, we use a higher-order function. Clojure provides
+ reduce
for the times when we need to accumulate data. We can implement our sum
+ funciton using reduce
like so:
+
+
+
+ (defn sum [xs] (reduce (fn [accum x] (+ accum x)) 0 xs))
+
+ reduce
an accumulator function, initial value for the accumulator,
+ and list of things to reduce. Try out our new function: (sum [1 2 3])
.
+ (fn [accum x] (+ accum x))
+ above, but it it this case we could simply use +
:
+
+
+
+ (defn sum [xs] (reduce + 0 xs))
+
+
+
+
+ (defn sum [xs] (reduce + xs))
+
+ +
, when called with no arguments, returns 0; try it:
+ (+)
.
+ reduce
added to our toolkit, we can implement
+ the eliminate
function. This function should take a grid, list of cells, and value to eliminate,
+ and return a new grid with this value eliminated from the specified cells:
+
+
+
+ (defn eliminate [grid cells value-to-eliminate]
+ ...)
+
+
+
+
+ (defn eliminate-one [grid cell value-to-eliminate]
+ (let [ix (cell-index cell)
+ cell-value (grid ix)]
+ ...))
+
+ nil
. If it is an integer different
+ than the one we're trying to eliminate, we should return the grid
+ unchanged:
+
+
+
+ (if (number? cell-value)
+ (if (= cell-value value-to-eliminate) nil grid))
+
+ value-to-eliminate
+ from the candidates. If this results in an empty set, we have again reached
+ an inconsistent state and should return nil
. Otherwise, we return a grid
+ with the new candidates for the cell:
+
+
+
+ (let [new-candidates (disj cell-value value-to-eliminate)]
+ (if (empty? new-candidates)
+ nil
+ (assoc grid ix new-candidates)))
+
+
+
+
+ (defn eliminate-one [grid cell value-to-eliminate]
+ (let [ix (cell-index cell)
+ cell-value (grid ix)]
+ (if (number? cell-value)
+ (if (= cell-value value-to-eliminate)
+ nil
+ grid)
+ (let [new-candidates (disj cell-value value-to-eliminate)]
+ (if (empty? new-candidates)
+ nil
+ (assoc grid ix new-candidates))))))
+
+ (render-grid grid)
. Cell A1 has only one candidate:
+ (get grid (cell-index "A1"))
. What happens if we try to eliminate it?
+
+
+
+ (eliminate-one grid "A1" 5)
+
+ eliminate-one
function
+ returns nil
to indicate that we have reached an invalid state.
+ eliminate
function using eliminate-one
+ and reduce
:
+
+
+
+ (defn eliminate [grid cells value-to-eliminate]
+ (reduce (fn [accum cell] (eliminate-one accum cell value-to-eliminate))
+ grid
+ cells))
+
+
+
+
+ (eliminate grid ["A2" "A3" "A4" "A5" "A6" "A7" "A8" "A9"] 5)
+
+
+
+ (eliminate grid ["A1" "A2" "A3" "A4" "A5"] 5)
+
+ eliminate-one
is returning nil
when
+ we try to eliminate 5 as a value for A1, and reduce
carries on looping
+ with a nil
accumulator. We need a way to short-circuit the reduce
+ when we run into a nil
value:
+
+
+ (defn eliminate [grid cells value-to-eliminate]
+ (reduce (fn [accum cell]
+ (let [new-accum (eliminate-one accum cell value-to-eliminate)]
+ (if (nil? new-accum)
+ (reduced new-accum)
+ new-accum)))
+ grid
+ cells))
+
+ nil
value of the new accumulator with reduced
+ causes reduce
to terminate immediately with that value. Let's see if that
+ has worked:
+
+
+ (eliminate grid ["A1" "A2" "A3" "A4" "A5" "A6" "A7" "A8" "A9"] 5)
+
+ eliminate
done.
+ assign
function is a bit simpler: when
+ assigning a value to a cell, we have to check that the value is in
+ the candidate list for that cell and, if it is, eliminate it as a candidate
+ from the peers and replace the candidate set with the assigned value.
+
+
+
+ (defn assign [grid cell value]
+ (let [ix (cell-index cell)]
+ (when (contains? (grid ix) value)
+ (when-let [new-grid (eliminate grid (peers cell) value)]
+ (assoc new-grid ix value)))))
+
+ when
for the first time. This is just an if
+ statement without an else clause - if the condidion we're testing is
+ not true, when
returns nil
, which is just what we
+ want to indicate an inconsistent state.
+ when-let
for the first time. You guessed it - this combines
+ when
and let
! If we enjoy typing, we could have do things
+ the long way:
+
+
+
+ (if (contains? (grid ix) value)
+ (let [new-grid (eliminate grid (peers cell) value)]
+ (if new-grid
+ (assoc new-grid ix value)
+ nil))
+ nil)
+
+ assign
function:
+
+
+
+ (assign grid "A1" 5)
+
+
+
+
+ (assign grid "A2" 5)
+
+ assign
+ function is returning the desired nil
.
+ assign
and eliminate
functions are the building
+ blocks we need to solve a Sudoku grid. We start by looking for a cell with only
+ one candidate and assigning that value to the cell. Then we recur...
+ recur
needs a termination condition. In
+ this case, we need to stop when the grid is solved, i.e. when the
+ value in every cell is a number (not a set of candidates).
+
+
+
+ (defn solved? [grid]
+ (every? number? grid))
+
+ false
for our grid:
+
+
+
+ (solved? grid)
+
+
+
+
+ (defn singleton? [cell-value]
+ (and (set? cell-value)
+ (= (count cell-value) 1)))
+
+
+
+
+ (defn find-singleton [grid]
+ (first
+ (for [row "ABCDEFGHI"
+ col "123456789"
+ :let [cell (str row col)]
+ :when (singleton? (grid (cell-index cell)))]
+ cell)))
+
+
+
+
+ (defn solve [grid]
+ (cond
+ (nil? grid) nil
+ (solved? grid) grid
+ :else (if-let [singleton-cell (find-singleton grid)]
+ (let [value (first (grid (cell-index singleton-cell)))
+ next-grid (assign grid singleton-cell value)]
+ (recur next-grid))
+ grid)))
+
+
+
+
+ (render-grid (solve grid))
+
+
+
+
+ (def hard-grid (parse-puzzle "008601000
+ 600000003
+ 000048506
+ 040000600
+ 780020091
+ 001000030
+ 109870000
+ 200000007
+ 000209100"))
+
+ (render-grid (solve hard-grid))
.
+ solve
function:
+
+
+
+ 1 (defn solve [grid]
+ 2 (cond
+ 3 (nil? grid) nil
+ 4 (solved? grid) grid
+ 5 :else (if-let [singleton-cell (find-singleton grid)]
+ 6 (let [value (first (grid (cell-index singleton-cell)))
+ 7 next-grid (assign grid singleton-cell value)]
+ 8 (recur next-grid))
+ 9 grid)))
+
+ next
to continue.
+ find-singleton
, but with a different filter predicate:
+
+
+
+ (defn find-unsolved [grid]
+ (first
+ (for [row "ABCDEFGHI"
+ col "123456789"
+ :let [cell (str row col)
+ cell-value (grid (cell-index cell))]
+ :when (and (set? cell-value) (not (singleton? cell-value)))]
+ cell)))
+
+ (find-unsolved hard-grid)
.
+ solve
function:
+
+
+
+ (defn solve [grid]
+ (cond
+ (nil? grid) nil
+ (solved? grid) grid
+ :else (if-let [singleton-cell (find-singleton grid)]
+ (let [value (first (grid (cell-index singleton-cell)))
+ next-grid (assign grid singleton-cell value)]
+ (recur next-grid))
+ (let [cell (find-unsolved grid)
+ candidates (grid (cell-index cell))]
+ (first (remove nil?
+ (map (fn [candidate]
+ (solve (assign grid cell candidate)))
+ candidates)))))))
+
+ map
to try to solve
+ the grid for each candidate in turn. When a candidate results in an inconsistent
+ state, solve
will return nil
. We remove these nil values
+ and return the first solution.
+
+
+
+ (render-grid (solve hard-grid))
+
+ defn
.
(conj v 5)
.
v
- it is still the vector
+ Now examine v
- it is still the vector
[1 2 3 4]
. Clojure has returned a new vector
with the value 5
added no the end. Clojure uses
structural sharing to manage these immutable data
diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj
index baa6690..ee2b239 100644
--- a/src/tryclojure/views/home.clj
+++ b/src/tryclojure/views/home.clj
@@ -22,7 +22,7 @@
[:p.bottom
"Here is our only disclaimer: this site is an introduction to Clojure, not a generic Clojure REPL. "
"You won't be able to do everything in it that you could do in your local interpreter. "
- "Also, the interpreter deletes the data that you enter if you define too many things, or after 15 minutes."]
+ "Also, the interpreter deletes the data that you enter if you define too many things, or after a period of inactivity."]
[:p.bottom
"TryClojure is written in Clojure and JavaScript with "
(link-to "https://github.com/weavejester/compojure" "Compojure") ", "
@@ -51,7 +51,7 @@
[:div#wrapper
[:div.github-fork-ribbon-wrapper.right
[:div.github-fork-ribbon
- (link-to "https://github.com/Raynes/tryclojure" "Fork me on GitHub")]]
+ (link-to "https://github.com/ray1729/tryclojure" "Fork me on GitHub")]]
[:div#content
[:div#header
[:h1
@@ -64,14 +64,4 @@
[:a#about.buttons.last "about"]]
[:div#changer (home-html)]]
[:div.footer
- [:p.bottom "©2011-2012 Anthony Grimes and numerous contributors."]]
- (javascript-tag
- "var _gaq = _gaq || [];
- _gaq.push(['_setAccount', 'UA-27340918-1']);
- _gaq.push(['_trackPageview']);
-
- (function() {
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- })();")]]]))
+ [:p.bottom "©2011-2012 Anthony Grimes and numerous contributors. Sudoku tutorial ©2014 Ray Miller."]]]]]))
From 5b06fb733b63969fa4db290e0657ee6eb6b6d2e5 Mon Sep 17 00:00:00 2001
From: Ray Miller def
binds the newly defined function to a name.
defn
; type
@@ -150,7 +150,7 @@
elements, you actually get a brand new list. (Fortunately,
Clojure is amazingly efficient at creating new lists). In
general, Clojure encourages you to have as little mutable state
- as possible. For example, instead qof "for" loops and other
+ as possible. For example, instead of "for" loops and other
state-changing constructs, most of the time you'll see functions
doing transformations on immutable data and returning new
collections, without changing the old one.
@@ -601,7 +601,7 @@
+---+---+---+
@@ -1168,7 +1168,8 @@
Hmm. In this case,
eliminate-one
is returning nil
when
we try to eliminate 5 as a value for A1, and reduce
carries on looping
with a nil
accumulator. We need a way to short-circuit the reduce
- when we run into a nil
value:
+ when we run into a nil
value. Try this new version
+ of eliminate
:
@@ -1356,7 +1357,9 @@
(render-grid (solve hard-grid))
.
+ Try solving this grid:
+ (render-grid (solve hard-grid))
.
+ (Again, it may take a few seconds.)
solve
function:
+ the original one! Let's take a look at our solve
+ function so far:
From 80ba840c5a5df9771743feeb2ae6db369918e497 Mon Sep 17 00:00:00 2001
From: Ray Miller
next
in the REPL to begin.for
to calculate all the possible scores for two dice.
+
+
+
+ (for [x (range 1 7) y (range 1 7)] (+ x y))
+
+ for
must be a vector with an even number of forms,
+ and the second is a function. The elements of the vector come in pairs: label then
+ sequence of values which will be assigned to the label. Thus in this
+ case the label x
will assigned the values 1, then 2 then 3
(defn row-render-values [grid row]
- (for [col (range 1 10) :let [cell (str row col)]]
- (render-cell (get grid (cell-index cell)))))
+ (for [col (range 1 10)]
+ (let [cell (str row col)]
+ (render-cell (get grid (cell-index cell))))))
partiiton
+ interpose pipe symbols. Luckily, Clojure comes with partition
and interpose
functions! Try:
From dd43215dfd9586c947735289416a82b0a88fa354 Mon Sep 17 00:00:00 2001
From: Ray Miller
cell-index
function. Can you
- complete this function definition?
+ We now have all the tools we need to write
+ our cell-index
function. This function should
+ return an integer between 0 and 80 (the index of the specified
+ cell in our 81-element vector). Can you complete this function
+ definition?
+
From 38f2bb590beefc3ea48fd73f8ad50d2b90a314ee Mon Sep 17 00:00:00 2001
From: Ray Miller
recur
again here, but this time
+ there's no opening loop
. It turns out that
+ functions also introduce a point we can recur to, and
+ using recur
in a function body like this simply
+ calls the function again with new arguments. Here, (recur
+ next-grid
is almost the same as (solve next-grid)
,
+ but we're giving the Clojure compiler an extra hint that allows it to
+ optimise the recursion to reduce stack consumption.
+ true
to continue.
for
below. This
@@ -663,18 +663,64 @@
for
must be a vector with an even number of forms,
- and the second is a function. The elements of the vector come in pairs: label then
- sequence of values which will be assigned to the label. Thus in this
- case the label x
will assigned the values 1, then 2 then 3 for
must be a vector with an
+ even number of forms, and the second is an expression - it can
+ be a constant or a function referencing the variables introduced
+ by for
. The elements of the vector come in pairs:
+ label then sequence of values which will be assigned to the
+ label. Thus in this case the label x
will assigned
+ the values 1, then 2 then 3 next
to continue.
+ for
function also accepts a couple of modifiers
+ introduced by the keywords :let
+ and :when
. We can use :let
to introduce
+ a local variable:
+
+
+
+ (for [x (range 1 7) y (range 1 7) :let [sum (+ x y)]]
+ sum)
+
+ :when
modifier introduces a filter; if we only
+ want the dice scores when the sum is even, we can write:
+
+
+
+ (for [x (range 1 7) y (range 1 7)
+ :let [sum (+ x y)]
+ :when (even? sum)]
+ sum)
+
+ for
to construct the cell names; for
+ example, to get the indices of all the cells in row D:
+
+
+
+ (for [col (range 1 10) :let [ix (cell-index (str "D" col))]]
+ ix)
+
+
(defn row-render-values [grid row]
- (for [col (range 1 10)]
- (let [cell (str row col)]
- (render-cell (get grid (cell-index cell))))))
+ (for [col (range 1 10) :let [ix (cell-index (str row col))]]
+ (render-cell (get grid ix))))
-
(let [[row col] (map str "A3") ...)
+ (let [[row col] (map str "A3")] ...)
let
). Putting this
From a14e0d2d4786db938990d901ba65cf62543ec8b2 Mon Sep 17 00:00:00 2001
From: Ray Miller (fn [x] (* x x))
+
+
(fn [x] (* x x))
+ for
to construct the cell names; for
From 30fec79f2ba4046cb5b6f809f7c898c4e7d4e1cd Mon Sep 17 00:00:00 2001
From: Ray Miller (+ 3 3)
, it is a code fragment we
+ expect you to enter into the console above. You can do this
+ simply by clicking on the highlighted code. Code
+ fragments like this
are illustrative examples that
+ should not be entered in the REPL.
next
to skip
forward, back
to return to the previous step,
- and restart
to get back to the beginning. Let's get
- started: type next
.
+ and restart
to get back to the beginning. These
+ commands are intercepted by the console and are not
+ evaluated as Clojure.
+ next
.
(+ 3 3)
is a list that contains an operator, and then the operands.
+ (+ 3 3)
is a list that contains an operator, and then the operands.
Try out the same concept with the *
and -
operators.
+
(fn [x] (* x x))
((fn [x] (* x x)) 10)
.
defn
; type
-
+
to continue.
@@ -132,8 +145,9 @@
- (def square (fn [x] (* x x)))
+ (def another-square (fn [x] (* x x)))
square
function
- just like you called the old square
function.
+ Success! Now you can call this new another-square
function
+ just like you called the old square
function:
+ (another-square 7)
(map inc [1 2 3 4])
to continue.
+
+
+ (def m {:coffee 1.10 :tea 0.90 :chocolate 1.20})
+
+ get
function to look things up in
+ our map: (get m :tea)
.
+ (m :tea)
.
+ (:tea m)
+
+
+
+ (get m :cappuccino)
+
+ nil
. An optional third argument
+ to get
changes the not-found value:
+
+
+
+ (get m :cappuccino 1.50)
+
+ get
to look up values in a vector -
+ in this case, the index in the vector acts as the key.
+
+
+
+ (get [:tea :coffee :chocolate] 0)
+
+ get
a value from a set?
+
+
+
+ (get #{"peas" "rice" "gravy"} "rice")
+
+
+
+
+ (get #{"peas" "rice" "gravy"} "beans")
+
+ (range end)
,
+ surprise you. When called with a single argument, (range end)
,
it returns a list of integers from 0 up to (but not
- including) end
.
+ including) end
.
(range start end)
, it returns the
+ (range start end)
, it returns the
numbers from start
up to (but not
- including) end
. An optional third argument
+ including) end
. An optional third argument
specifies the step size; try
(range 1 50 5)
.
set
function to turn the
- sequence generated by range
into a set. Type
+ sequence generated by range
into a set: try it
+ (set (range 1 10))
.
+ (def candidates (set (range 1 10)))
to
continue.
(.indexOf "ABCDEFGHI" "C")
.
(.indexOf "123456789" "3")
. We're working
towards a function that we can call like (cell-index "A3")
, so the final piece
of the puzzle is to split "A3"
into its row and column components "A"
and "3"
. There are many ways to do this, but here's one to try:
- (map str "A3")
.
+ (seq "A3")
.
"A3"
behaves like a list of characters; try
- (seq "A3")
in the REPL.
+ That turned our string into a sequence of characters.
+ Unfortunately the indexOf
method expects a string
+ (or an integer) as its second argument, so we need one more
+ step: (map str (seq "A3"))
.
map
function expects a
- sequence as its second argument, so it automatically coerces the
- string to a sequence of characters for us.
+ sequence as its second argument, so we can drop
+ the seq
from the above expression. When we pass in
+ a string, Clojure will add an implicit seq
for us:
+ (map str "A3")
+ def
used to define a top-level variable,
+ but sometimes we don't want our variables to leak out into
+ global scope - we need local variables. In Clojure, these are
+ introduced with let
:
+
+
+
+ (let [x 7]
+ (square x))
+
+ let
, x
has no meaning:
+ (square x)
.
+ (let [[row col] (map str "A3")] ...)
let
). Putting this
- all together, we have:
+ (note the extra set of square brackets inside
+ the let
).
+
next
to continue.
+
+
(let [[row col] (map str "A3")
row-index (.indexOf "ABCDEFGHI" row)
@@ -370,6 +488,11 @@
...))
cell-index
:
-
+
(defn cell-index
[cell]
@@ -394,7 +517,7 @@
each cell. Let's define the empty grid, where the values of all cells are
unknown:
-
+
(def empty-grid (vec (repeat 81 candidates)))
@@ -421,7 +544,7 @@
use 0
to represent an unknown value, we can
represent the puzzle like so:
-
+
(def puzzle "530070000
600195000
@@ -439,7 +562,7 @@
use to initialize our grid. Let's use a regular expression to
pick out the digits:
-
+
(re-seq #"\d" puzzle)
@@ -457,7 +580,7 @@
(ClassName/methodName arg ...)
Using this, we can
define:
-
+
(defn parse-int [s] (Integer/parseInt s))
@@ -465,7 +588,7 @@
+
(map parse-int (re-seq #"\d" puzzle))
@@ -508,7 +631,7 @@
candidates-for
function:
+
(defn candidates-for [d]
(if (zero? d) candidates (hash-set d)))
@@ -517,12 +640,12 @@
d
:
+
-
(candidates-for 3)
+
(candidates-for 0)
@@ -533,7 +656,7 @@
With this helper function in hand, we can use map
to build our grid. Try:
-
+
(def grid (map candidates-for (map parse-int (re-seq #"\d" puzzle))))
@@ -544,7 +667,7 @@
the solution, and you'd be right! We can use Clojure's comp
function to compose
the two functions:
-
+
(def grid (map (comp candidates-for parse-int) (re-seq #"\d" puzzle)))
@@ -554,7 +677,7 @@
grid
:
+
(type grid)
@@ -575,7 +698,7 @@
Let's put everything together into a function that parses a puzzle and returns the candidates
grid as a vector:
-
+
(defn parse-puzzle [puzzle]
(mapv (comp candidates-for parse-int)
@@ -585,7 +708,7 @@
+
(def grid (parse-puzzle puzzle))
@@ -593,7 +716,7 @@
get
:
+
(get grid (cell-index "D5"))
@@ -602,7 +725,7 @@
Clojure vectors also act as functions that take an index as their
first argument and return the value at that index; try it now:
-
+
(grid (cell-index "D5"))
@@ -632,7 +755,7 @@
We'll start by writing a function to render a cell. If the cell is a number, or a set of
candidates with only one element, we should render the number, otherwise a dot.
-
+
(defn render-cell [x]
(cond
@@ -664,7 +787,7 @@
for
to calculate all the possible scores for two dice.
+
(for [x (range 1 7) y (range 1 7)] (+ x y))
@@ -683,14 +806,14 @@
Type next
to continue.
for
function also accepts a couple of modifiers
introduced by the keywords :let
and :when
. We can use :let
to introduce
a local variable:
+
(for [x (range 1 7) y (range 1 7) :let [sum (+ x y)]]
sum)
@@ -700,7 +823,7 @@
The
:when
modifier introduces a filter; if we only
want the dice scores when the sum is even, we can write:
-
+
+
(for [x (range 1 7) y (range 1 7)
:let [sum (+ x y)]
@@ -708,12 +831,27 @@
sum)
:while
, which can be used to exit
+ the for
early:
+
+
+
+ (for [x (range 1 7) y (range 1 7)
+ :let [sum (+ x y)]
+ :while (< sum 8)]
+ sum)
+
+ for
to construct the cell names; for
example, to get the indices of all the cells in row D:
+
(for [col (range 1 10) :let [ix (cell-index (str "D" col))]]
ix)
@@ -723,7 +861,7 @@
This leads us to the following function, which takes the row we
are interested in as its second argument:
-
+
(defn row-render-values [grid row]
(for [col (range 1 10) :let [ix (cell-index (str row col))]]
@@ -740,13 +878,13 @@
interpose pipe symbols. Luckily, Clojure comes with
partition
and interpose
functions! Try:
-
+
(partition 3 (range 9))
+
(interpose "|" (partition 3 (range 9)))
@@ -755,7 +893,7 @@
+
["|" (interpose "|" (partition 3 (range 9))) "|"]
@@ -768,7 +906,7 @@
structure, not a string we can print. Once again, Clojure's core
library has a function that comes to our rescue:
-
+
(flatten ["|" (interpose "|" (partition 3 (range 9))) "|"])
@@ -778,13 +916,13 @@
the list returned by flatten
, we won't get quite
the right output. Try:
-
+
(str ["A" "ba" "cus"])
+
(str "A" "ba" "cus")
flatten
, making it behave as if we'd
called str
with multiple arguments:
+
(apply str (flatten ["|" (interpose "|" (partition 3 (range 9))) "|"]))
@@ -805,7 +943,7 @@
+
(defn render-grid-row [grid row]
(apply str
@@ -824,7 +962,7 @@
Hopefully by now you'll realise that this is a prime candidate
for
map
. We could write:
-
+
(map (fn [row] (render-grid-row grid row)) "ABCDEFGHI")
@@ -835,7 +973,7 @@
create a new function that is just like render-grid-row
,
but with the first argument pre-populated.
-
+
(map (partial render-grid-row grid) "ABCDEFGHI")
@@ -846,7 +984,7 @@
When it comes to rendering the grid, we need to print a horizontal separator at the start
and end, and between every 3 rows.
-
+
(def hsep "+---+---+---+")
@@ -855,7 +993,7 @@
We can use flatten
, interpose
and partition
to build the grid, like we did for each row:
-
+
(flatten [hsep
(interpose hsep
@@ -866,7 +1004,7 @@
render-grid
function.
+
(defn render-grid [grid]
(let [hsep "+---+---+---+"
@@ -876,7 +1014,7 @@
(render-grid grid)
.
+ To continue, enter (render-grid grid)
.
row-peers
:
+
(defn row-peers [cell]
(let [[row _] (map str cell)]
@@ -917,7 +1055,7 @@
col-peers
is almost the same:
+
(defn col-peers [cell]
(let [[_ col] (map str cell)]
@@ -934,7 +1072,7 @@
could do some arithmetic to work this out, but we'll keep things simple and just
hard-code a lookup.
-
+
(defn square-peers [cell]
(let [[row col] (map str cell)
@@ -962,7 +1100,7 @@
We need to return all the peers for a given cell, which we do by concatenating the
results of the three simpler functions; try:
-
+
(concat (square-peers "D5") (row-peers "D5") (col-peers "D5"))
@@ -970,7 +1108,7 @@
+
(defn peers [cell]
(distinct (concat (square-peers cell)
@@ -985,7 +1123,7 @@
Finally, a cell is not a peer of itself, so we have to remove it from the list.
Here's one way to do it:
-
+
(defn peers [cell]
(remove (fn [x] (= x cell))
@@ -1002,7 +1140,7 @@
some sets:
(#{"a" "b" "c"} "b")
or (#{"a" "b" "c"} "d")
.
This allows us to write:
-
+
(defn peers [cell]
(remove #{cell} (distinct (concat (square-peers cell)
@@ -1022,7 +1160,7 @@
recursion. Here's a simple example of recursion using
Clojure's
loop
and recur
:
-
+
(loop [i 0]
(println (str "Iteration " i))
@@ -1042,7 +1180,7 @@
We can also accumulate counters or lists in a loop; for example, to sum the numbers from
1
to n
, we could write:
-
+
(defn triangle-number [n]
(loop [accum 0 n n]
@@ -1061,7 +1199,7 @@
more general
sum
function to add up an arbitrary list of
numbers:
-
+
(defn sum [xs]
(loop [accum 0 xs xs]
@@ -1084,7 +1222,7 @@
reduce
for the times when we need to accumulate data. We can implement our sum
funciton using reduce
like so:
-
+
(defn sum [xs] (reduce (fn [accum x] (+ accum x)) 0 xs))
@@ -1099,7 +1237,7 @@
value of the accumulator. We used (fn [accum x] (+ accum x))
above, but it it this case we could simply use +
:
-
+
(defn sum [xs] (reduce + 0 xs))
@@ -1107,7 +1245,7 @@
+
(defn sum [xs] (reduce + xs))
@@ -1123,7 +1261,7 @@
the eliminate
function. This function should take a grid, list of cells, and value to eliminate,
and return a new grid with this value eliminated from the specified cells:
-
+
(defn eliminate [grid cells value-to-eliminate]
...)
@@ -1133,7 +1271,7 @@
We'll start with a simple helper function to eliminate a value
from a single cell:
-
+
(defn eliminate-one [grid cell value-to-eliminate]
(let [ix (cell-index cell)
@@ -1150,7 +1288,7 @@
than the one we're trying to eliminate, we should return the grid
unchanged:
-
+
(if (number? cell-value)
(if (= cell-value value-to-eliminate) nil grid))
@@ -1162,7 +1300,7 @@
an inconsistent state and should return
nil
. Otherwise, we return a grid
with the new candidates for the cell:
-
+
(let [new-candidates (disj cell-value value-to-eliminate)]
(if (empty? new-candidates)
@@ -1174,7 +1312,7 @@
+
(defn eliminate-one [grid cell value-to-eliminate]
(let [ix (cell-index cell)
@@ -1193,7 +1331,7 @@
Recall our original puzzle:
(render-grid grid)
. Cell A1 has only one candidate:
(get grid (cell-index "A1"))
. What happens if we try to eliminate it?
-
+
(eliminate-one grid "A1" 5)
@@ -1209,7 +1347,7 @@
We can now write our eliminate
function using eliminate-one
and reduce
:
-
+
(defn eliminate [grid cells value-to-eliminate]
(reduce (fn [accum cell] (eliminate-one accum cell value-to-eliminate))
@@ -1221,7 +1359,7 @@
We know that cell A1 has the value 5, so we can try eliminating the value 5 from the
other cells in row A:
-
+
(eliminate grid ["A2" "A3" "A4" "A5" "A6" "A7" "A8" "A9"] 5)
@@ -1232,7 +1370,7 @@
So far, so good. But what happens if A1 also appears in the list of cells for
elimination?
-
+
(eliminate grid ["A1" "A2" "A3" "A4" "A5"] 5)
@@ -1244,7 +1382,7 @@
when we run into a nil
value. Try this new version
of eliminate
:
-
+
(defn eliminate [grid cells value-to-eliminate]
(reduce (fn [accum cell]
@@ -1261,7 +1399,7 @@
causes
reduce
to terminate immediately with that value. Let's see if that
has worked:
-
+
(eliminate grid ["A1" "A2" "A3" "A4" "A5" "A6" "A7" "A8" "A9"] 5)
@@ -1277,7 +1415,7 @@
the candidate list for that cell and, if it is, eliminate it as a candidate
from the peers and replace the candidate set with the assigned value.
-
+
(defn assign [grid cell value]
(let [ix (cell-index cell)]
@@ -1297,7 +1435,7 @@
when
and let
! If we enjoy typing, we could have do things
the long way:
-
+
(if (contains? (grid ix) value)
(let [new-grid (eliminate grid (peers cell) value)]
@@ -1310,7 +1448,7 @@
assign
function:
+
(assign grid "A1" 5)
@@ -1318,7 +1456,7 @@
+
(assign grid "A2" 5)
@@ -1340,7 +1478,7 @@
this case, we need to stop when the grid is solved, i.e. when the
value in every cell is a number (not a set of candidates).
-
+
(defn solved? [grid]
(every? number? grid))
@@ -1349,7 +1487,7 @@
false
for our grid:
+
(solved? grid)
@@ -1360,7 +1498,7 @@
We also need to find the first singleton in the grid. By singleton, We
mean a set with one element:
-
+
(defn singleton? [cell-value]
(and (set? cell-value)
@@ -1370,7 +1508,7 @@
+
(defn find-singleton [grid]
(first
@@ -1389,7 +1527,7 @@
+
(defn solve [grid]
(cond
@@ -1415,7 +1553,7 @@
+
(render-grid (solve grid))
@@ -1426,7 +1564,7 @@
Yay, it worked! This simple process of assignment and elimination solved this puzzle.
But we're not done yet... Let's define a harder puzzle:
-
+
(def hard-grid (parse-puzzle "008601000
600000003
@@ -1460,7 +1598,7 @@
the original one! Let's take a look at our
solve
function so far:
-
+
1 (defn solve [grid]
2 (cond
@@ -1484,7 +1622,7 @@
Let's start by writing a function to find the first unsolved cell. This is similar to
find-singleton
, but with a different filter predicate:
-
+
(defn find-unsolved [grid]
(first
@@ -1504,7 +1642,7 @@
solve
function:
+
(defn solve [grid]
(cond
@@ -1531,7 +1669,7 @@
+
(render-grid (solve hard-grid))
diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj
index 9b9a7ca..e1adea1 100644
--- a/src/tryclojure/views/home.clj
+++ b/src/tryclojure/views/home.clj
@@ -36,7 +36,7 @@
[:p.bottom
"Welcome to Clojure! "
"You can see a Clojure interpreter above - we call it a REPL."]
- [:p.bottom "Type next
in the REPL to begin." ]))
+ [:p.bottom "Type next
in the REPL to begin." ]))
(defn root-html []
(html5
From 60ce0b1629ba44446c55f6dd9556b98090086c8f Mon Sep 17 00:00:00 2001
From: Tom Crinson next
in the REPL to begin.true
to continue.
for
below. This
@@ -668,18 +668,64 @@
for
must be a vector with an even number of forms,
- and the second is a function. The elements of the vector come in pairs: label then
- sequence of values which will be assigned to the label. Thus in this
- case the label x
will assigned the values 1, then 2 then 3 for
must be a vector with an
+ even number of forms, and the second is an expression - it can
+ be a constant or a function referencing the variables introduced
+ by for
. The elements of the vector come in pairs:
+ label then sequence of values which will be assigned to the
+ label. Thus in this case the label x
will assigned
+ the values 1, then 2 then 3 next
to continue.
+ for
function also accepts a couple of modifiers
+ introduced by the keywords :let
+ and :when
. We can use :let
to introduce
+ a local variable:
+
+
+
+ (for [x (range 1 7) y (range 1 7) :let [sum (+ x y)]]
+ sum)
+
+ :when
modifier introduces a filter; if we only
+ want the dice scores when the sum is even, we can write:
+
+
+
+ (for [x (range 1 7) y (range 1 7)
+ :let [sum (+ x y)]
+ :when (even? sum)]
+ sum)
+
+ for
to construct the cell names; for
+ example, to get the indices of all the cells in row D:
+
+
+
+ (for [col (range 1 10) :let [ix (cell-index (str "D" col))]]
+ ix)
+
+
(defn row-render-values [grid row]
- (for [col (range 1 10)]
- (let [cell (str row col)]
- (render-cell (get grid (cell-index cell))))))
+ (for [col (range 1 10) :let [ix (cell-index (str row col))]]
+ (render-cell (get grid ix))))
-
(let [[row col] (map str "A3") ...)
+ (let [[row col] (map str "A3")] ...)
let
). Putting this
From 1b30ed478db543023be495bf109a970844e94bc8 Mon Sep 17 00:00:00 2001
From: Ray Miller (fn [x] (* x x))
+
+
(fn [x] (* x x))
+ for
to construct the cell names; for
From 9d6837af54aed03664c541ea79a29cf9ec55cf57 Mon Sep 17 00:00:00 2001
From: Ray Miller (+ 3 3)
, it is a code fragment we
+ expect you to enter into the console above. You can do this
+ simply by clicking on the highlighted code. Code
+ fragments like this
are illustrative examples that
+ should not be entered in the REPL.
next
to skip
forward, back
to return to the previous step,
- and restart
to get back to the beginning. Let's get
- started: type next
.
+ and restart
to get back to the beginning. These
+ commands are intercepted by the console and are not
+ evaluated as Clojure.
+ next
.
(+ 3 3)
is a list that contains an operator, and then the operands.
+ (+ 3 3)
is a list that contains an operator, and then the operands.
Try out the same concept with the *
and -
operators.
+
(fn [x] (* x x))
((fn [x] (* x x)) 10)
.
defn
; type
-
+
to continue.
@@ -132,8 +145,9 @@
- (def square (fn [x] (* x x)))
+ (def another-square (fn [x] (* x x)))
square
function
- just like you called the old square
function.
+ Success! Now you can call this new another-square
function
+ just like you called the old square
function:
+ (another-square 7)
(map inc [1 2 3 4])
to continue.
+
+
+ (def m {:coffee 1.10 :tea 0.90 :chocolate 1.20})
+
+ get
function to look things up in
+ our map: (get m :tea)
.
+ (m :tea)
.
+ (:tea m)
+
+
+
+ (get m :cappuccino)
+
+ nil
. An optional third argument
+ to get
changes the not-found value:
+
+
+
+ (get m :cappuccino 1.50)
+
+ get
to look up values in a vector -
+ in this case, the index in the vector acts as the key.
+
+
+
+ (get [:tea :coffee :chocolate] 0)
+
+ get
a value from a set?
+
+
+
+ (get #{"peas" "rice" "gravy"} "rice")
+
+
+
+
+ (get #{"peas" "rice" "gravy"} "beans")
+
+ (range end)
,
+ surprise you. When called with a single argument, (range end)
,
it returns a list of integers from 0 up to (but not
- including) end
.
+ including) end
.
(range start end)
, it returns the
+ (range start end)
, it returns the
numbers from start
up to (but not
- including) end
. An optional third argument
+ including) end
. An optional third argument
specifies the step size; try
(range 1 50 5)
.
set
function to turn the
- sequence generated by range
into a set. Type
+ sequence generated by range
into a set: try it
+ (set (range 1 10))
.
+ (def candidates (set (range 1 10)))
to
continue.
(.indexOf "ABCDEFGHI" "C")
.
(.indexOf "123456789" "3")
. We're working
towards a function that we can call like (cell-index "A3")
, so the final piece
of the puzzle is to split "A3"
into its row and column components "A"
and "3"
. There are many ways to do this, but here's one to try:
- (map str "A3")
.
+ (seq "A3")
.
"A3"
behaves like a list of characters; try
- (seq "A3")
in the REPL.
+ That turned our string into a sequence of characters.
+ Unfortunately the indexOf
method expects a string
+ (or an integer) as its second argument, so we need one more
+ step: (map str (seq "A3"))
.
map
function expects a
- sequence as its second argument, so it automatically coerces the
- string to a sequence of characters for us.
+ sequence as its second argument, so we can drop
+ the seq
from the above expression. When we pass in
+ a string, Clojure will add an implicit seq
for us:
+ (map str "A3")
+ def
used to define a top-level variable,
+ but sometimes we don't want our variables to leak out into
+ global scope - we need local variables. In Clojure, these are
+ introduced with let
:
+
+
+
+ (let [x 7]
+ (square x))
+
+ let
, x
has no meaning:
+ (square x)
.
+ (let [[row col] (map str "A3")] ...)
let
). Putting this
- all together, we have:
+ (note the extra set of square brackets inside
+ the let
).
+
next
to continue.
+
+
(let [[row col] (map str "A3")
row-index (.indexOf "ABCDEFGHI" row)
@@ -370,6 +488,11 @@
...))
cell-index
:
-
+
(defn cell-index
[cell]
@@ -394,7 +517,7 @@
each cell. Let's define the empty grid, where the values of all cells are
unknown:
-
+
(def empty-grid (vec (repeat 81 candidates)))
@@ -421,7 +544,7 @@
use 0
to represent an unknown value, we can
represent the puzzle like so:
-
+
(def puzzle "530070000
600195000
@@ -439,7 +562,7 @@
use to initialize our grid. Let's use a regular expression to
pick out the digits:
-
+
(re-seq #"\d" puzzle)
@@ -457,7 +580,7 @@
(ClassName/methodName arg ...)
Using this, we can
define:
-
+
(defn parse-int [s] (Integer/parseInt s))
@@ -465,7 +588,7 @@
+
(map parse-int (re-seq #"\d" puzzle))
@@ -508,7 +631,7 @@
candidates-for
function:
+
(defn candidates-for [d]
(if (zero? d) candidates (hash-set d)))
@@ -517,12 +640,12 @@
d
:
+
-
(candidates-for 3)
+
(candidates-for 0)
@@ -533,7 +656,7 @@
With this helper function in hand, we can use map
to build our grid. Try:
-
+
(def grid (map candidates-for (map parse-int (re-seq #"\d" puzzle))))
@@ -544,7 +667,7 @@
the solution, and you'd be right! We can use Clojure's comp
function to compose
the two functions:
-
+
(def grid (map (comp candidates-for parse-int) (re-seq #"\d" puzzle)))
@@ -554,7 +677,7 @@
grid
:
+
(type grid)
@@ -575,7 +698,7 @@
Let's put everything together into a function that parses a puzzle and returns the candidates
grid as a vector:
-
+
(defn parse-puzzle [puzzle]
(mapv (comp candidates-for parse-int)
@@ -585,7 +708,7 @@
+
(def grid (parse-puzzle puzzle))
@@ -593,7 +716,7 @@
get
:
+
(get grid (cell-index "D5"))
@@ -602,7 +725,7 @@
Clojure vectors also act as functions that take an index as their
first argument and return the value at that index; try it now:
-
+
(grid (cell-index "D5"))
@@ -632,7 +755,7 @@
We'll start by writing a function to render a cell. If the cell is a number, or a set of
candidates with only one element, we should render the number, otherwise a dot.
-
+
(defn render-cell [x]
(cond
@@ -664,7 +787,7 @@
for
to calculate all the possible scores for two dice.
+
(for [x (range 1 7) y (range 1 7)] (+ x y))
@@ -683,14 +806,14 @@
Type next
to continue.
for
function also accepts a couple of modifiers
introduced by the keywords :let
and :when
. We can use :let
to introduce
a local variable:
+
(for [x (range 1 7) y (range 1 7) :let [sum (+ x y)]]
sum)
@@ -700,7 +823,7 @@
The
:when
modifier introduces a filter; if we only
want the dice scores when the sum is even, we can write:
-
+
+
(for [x (range 1 7) y (range 1 7)
:let [sum (+ x y)]
@@ -708,12 +831,27 @@
sum)
:while
, which can be used to exit
+ the for
early:
+
+
+
+ (for [x (range 1 7) y (range 1 7)
+ :let [sum (+ x y)]
+ :while (< sum 8)]
+ sum)
+
+ for
to construct the cell names; for
example, to get the indices of all the cells in row D:
+
(for [col (range 1 10) :let [ix (cell-index (str "D" col))]]
ix)
@@ -723,7 +861,7 @@
This leads us to the following function, which takes the row we
are interested in as its second argument:
-
+
(defn row-render-values [grid row]
(for [col (range 1 10) :let [ix (cell-index (str row col))]]
@@ -740,13 +878,13 @@
interpose pipe symbols. Luckily, Clojure comes with
partition
and interpose
functions! Try:
-
+
(partition 3 (range 9))
+
(interpose "|" (partition 3 (range 9)))
@@ -755,7 +893,7 @@
+
["|" (interpose "|" (partition 3 (range 9))) "|"]
@@ -768,7 +906,7 @@
structure, not a string we can print. Once again, Clojure's core
library has a function that comes to our rescue:
-
+
(flatten ["|" (interpose "|" (partition 3 (range 9))) "|"])
@@ -778,13 +916,13 @@
the list returned by flatten
, we won't get quite
the right output. Try:
-
+
(str ["A" "ba" "cus"])
+
(str "A" "ba" "cus")
flatten
, making it behave as if we'd
called str
with multiple arguments:
+
(apply str (flatten ["|" (interpose "|" (partition 3 (range 9))) "|"]))
@@ -805,7 +943,7 @@
+
(defn render-grid-row [grid row]
(apply str
@@ -824,7 +962,7 @@
Hopefully by now you'll realise that this is a prime candidate
for
map
. We could write:
-
+
(map (fn [row] (render-grid-row grid row)) "ABCDEFGHI")
@@ -835,7 +973,7 @@
create a new function that is just like render-grid-row
,
but with the first argument pre-populated.
-
+
(map (partial render-grid-row grid) "ABCDEFGHI")
@@ -846,7 +984,7 @@
When it comes to rendering the grid, we need to print a horizontal separator at the start
and end, and between every 3 rows.
-
+
(def hsep "+---+---+---+")
@@ -855,7 +993,7 @@
We can use flatten
, interpose
and partition
to build the grid, like we did for each row:
-
+
(flatten [hsep
(interpose hsep
@@ -866,7 +1004,7 @@
render-grid
function.
+
(defn render-grid [grid]
(let [hsep "+---+---+---+"
@@ -876,7 +1014,7 @@
(render-grid grid)
.
+ To continue, enter (render-grid grid)
.
row-peers
:
+
(defn row-peers [cell]
(let [[row _] (map str cell)]
@@ -917,7 +1055,7 @@
col-peers
is almost the same:
+
(defn col-peers [cell]
(let [[_ col] (map str cell)]
@@ -934,7 +1072,7 @@
could do some arithmetic to work this out, but we'll keep things simple and just
hard-code a lookup.
-
+
(defn square-peers [cell]
(let [[row col] (map str cell)
@@ -962,7 +1100,7 @@
We need to return all the peers for a given cell, which we do by concatenating the
results of the three simpler functions; try:
-
+
(concat (square-peers "D5") (row-peers "D5") (col-peers "D5"))
@@ -970,7 +1108,7 @@
+
(defn peers [cell]
(distinct (concat (square-peers cell)
@@ -985,7 +1123,7 @@
Finally, a cell is not a peer of itself, so we have to remove it from the list.
Here's one way to do it:
-
+
(defn peers [cell]
(remove (fn [x] (= x cell))
@@ -1002,7 +1140,7 @@
some sets:
(#{"a" "b" "c"} "b")
or (#{"a" "b" "c"} "d")
.
This allows us to write:
-
+
(defn peers [cell]
(remove #{cell} (distinct (concat (square-peers cell)
@@ -1022,7 +1160,7 @@
recursion. Here's a simple example of recursion using
Clojure's
loop
and recur
:
-
+
(loop [i 0]
(println (str "Iteration " i))
@@ -1042,7 +1180,7 @@
We can also accumulate counters or lists in a loop; for example, to sum the numbers from
1
to n
, we could write:
-
+
(defn triangle-number [n]
(loop [accum 0 n n]
@@ -1061,7 +1199,7 @@
more general
sum
function to add up an arbitrary list of
numbers:
-
+
(defn sum [xs]
(loop [accum 0 xs xs]
@@ -1084,7 +1222,7 @@
reduce
for the times when we need to accumulate data. We can implement our sum
funciton using reduce
like so:
-
+
(defn sum [xs] (reduce (fn [accum x] (+ accum x)) 0 xs))
@@ -1099,7 +1237,7 @@
value of the accumulator. We used (fn [accum x] (+ accum x))
above, but it it this case we could simply use +
:
-
+
(defn sum [xs] (reduce + 0 xs))
@@ -1107,7 +1245,7 @@
+
(defn sum [xs] (reduce + xs))
@@ -1123,7 +1261,7 @@
the eliminate
function. This function should take a grid, list of cells, and value to eliminate,
and return a new grid with this value eliminated from the specified cells:
-
+
(defn eliminate [grid cells value-to-eliminate]
...)
@@ -1133,7 +1271,7 @@
We'll start with a simple helper function to eliminate a value
from a single cell:
-
+
(defn eliminate-one [grid cell value-to-eliminate]
(let [ix (cell-index cell)
@@ -1150,7 +1288,7 @@
than the one we're trying to eliminate, we should return the grid
unchanged:
-
+
(if (number? cell-value)
(if (= cell-value value-to-eliminate) nil grid))
@@ -1162,7 +1300,7 @@
an inconsistent state and should return
nil
. Otherwise, we return a grid
with the new candidates for the cell:
-
+
(let [new-candidates (disj cell-value value-to-eliminate)]
(if (empty? new-candidates)
@@ -1174,7 +1312,7 @@
+
(defn eliminate-one [grid cell value-to-eliminate]
(let [ix (cell-index cell)
@@ -1193,7 +1331,7 @@
Recall our original puzzle:
(render-grid grid)
. Cell A1 has only one candidate:
(get grid (cell-index "A1"))
. What happens if we try to eliminate it?
-
+
(eliminate-one grid "A1" 5)
@@ -1209,7 +1347,7 @@
We can now write our eliminate
function using eliminate-one
and reduce
:
-
+
(defn eliminate [grid cells value-to-eliminate]
(reduce (fn [accum cell] (eliminate-one accum cell value-to-eliminate))
@@ -1221,7 +1359,7 @@
We know that cell A1 has the value 5, so we can try eliminating the value 5 from the
other cells in row A:
-
+
(eliminate grid ["A2" "A3" "A4" "A5" "A6" "A7" "A8" "A9"] 5)
@@ -1232,7 +1370,7 @@
So far, so good. But what happens if A1 also appears in the list of cells for
elimination?
-
+
(eliminate grid ["A1" "A2" "A3" "A4" "A5"] 5)
@@ -1244,7 +1382,7 @@
when we run into a nil
value. Try this new version
of eliminate
:
-
+
(defn eliminate [grid cells value-to-eliminate]
(reduce (fn [accum cell]
@@ -1261,7 +1399,7 @@
causes
reduce
to terminate immediately with that value. Let's see if that
has worked:
-
+
(eliminate grid ["A1" "A2" "A3" "A4" "A5" "A6" "A7" "A8" "A9"] 5)
@@ -1277,7 +1415,7 @@
the candidate list for that cell and, if it is, eliminate it as a candidate
from the peers and replace the candidate set with the assigned value.
-
+
(defn assign [grid cell value]
(let [ix (cell-index cell)]
@@ -1297,7 +1435,7 @@
when
and let
! If we enjoy typing, we could have do things
the long way:
-
+
(if (contains? (grid ix) value)
(let [new-grid (eliminate grid (peers cell) value)]
@@ -1310,7 +1448,7 @@
assign
function:
+
(assign grid "A1" 5)
@@ -1318,7 +1456,7 @@
+
(assign grid "A2" 5)
@@ -1340,7 +1478,7 @@
this case, we need to stop when the grid is solved, i.e. when the
value in every cell is a number (not a set of candidates).
-
+
(defn solved? [grid]
(every? number? grid))
@@ -1349,7 +1487,7 @@
false
for our grid:
+
(solved? grid)
@@ -1360,7 +1498,7 @@
We also need to find the first singleton in the grid. By singleton, We
mean a set with one element:
-
+
(defn singleton? [cell-value]
(and (set? cell-value)
@@ -1370,7 +1508,7 @@
+
(defn find-singleton [grid]
(first
@@ -1389,7 +1527,7 @@
+
(defn solve [grid]
(cond
@@ -1415,7 +1553,7 @@
+
(render-grid (solve grid))
@@ -1426,7 +1564,7 @@
Yay, it worked! This simple process of assignment and elimination solved this puzzle.
But we're not done yet... Let's define a harder puzzle:
-
+
(def hard-grid (parse-puzzle "008601000
600000003
@@ -1460,7 +1598,7 @@
the original one! Let's take a look at our
solve
function so far:
-
+
1 (defn solve [grid]
2 (cond
@@ -1484,7 +1622,7 @@
Let's start by writing a function to find the first unsolved cell. This is similar to
find-singleton
, but with a different filter predicate:
-
+
(defn find-unsolved [grid]
(first
@@ -1504,7 +1642,7 @@
solve
function:
+
(defn solve [grid]
(cond
@@ -1531,7 +1669,7 @@
+
(render-grid (solve hard-grid))
diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj
index 9b9a7ca..e1adea1 100644
--- a/src/tryclojure/views/home.clj
+++ b/src/tryclojure/views/home.clj
@@ -36,7 +36,7 @@
[:p.bottom
"Welcome to Clojure! "
"You can see a Clojure interpreter above - we call it a REPL."]
- [:p.bottom "Type next
in the REPL to begin." ]))
+ [:p.bottom "Type next
in the REPL to begin." ]))
(defn root-html []
(html5
From e04fba54b587a1e25fe9366e6d05b1372e146631 Mon Sep 17 00:00:00 2001
From: Jim Downing