diff --git a/.gitignore b/.gitignore index 102c661..31556e8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ lib classes *~ .lein-deps-sum +target/ +.nrepl-port +logs/ diff --git a/README.md b/README.md index 65cfeb7..8abafea 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,50 @@ TryClojure is a online Clojure REPL written using Noir and Chris Done's jquery c ## Usage -http://tryclj.com - To run it locally, use `lein ring server`. +## Online + +The original version of the tutorial is available at: http://tryclj.com + +We are running an instance of the extended tutorial on EC2: + +http://ec2-54-77-13-3.eu-west-1.compute.amazonaws.com:8801/ + +## Amazon EC2 setup notes + +Installed Ubuntu 14.04 LTS 64-bit AMI, login and: + + sudo apt-get install openjdk-7-jdk + cd $(mktemp -d) + wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein + chmod 0755 lein + sudo cp lein /usr/local/bin + lein version + sudo apt-get install git + sudo adduser --system tryclj --group --disabled-login --home /opt/tryclojure + cd /opt/tryclojure + sudo -u tryclj git clone https://github.com/ray1729/tryclojure.git + sudo -u tryclj mkdir tryclojure/logs + sudo sh -c 'cat > /etc/init/tryclj.conf' <= 0 && pageNumber < pages.length && pages[pageNumber].exitexpr) { + return function(data) { return data.expr == pages[pageNumber].exitexpr; } } -]; + else { + return function(data) { return false; } + + } +} function goToPage(pageNumber) { - if (pageNumber == currentPage || pageNumber < 0 || pageNumber >= pages.length) { - return; - } - - currentPage = pageNumber; - - var block = $("#changer"); - block.fadeOut(function(e) { - block.load("/tutorial", { 'page' : pages[pageNumber] }, function() { - block.fadeIn(); - changerUpdated(); - }); + if (pageNumber == currentPage || pageNumber < 0 || pageNumber >= pages.length) { + return; + } + + currentPage = pageNumber; + + var block = $("#changer"); + block.fadeOut(function(e) { + block.load("/tutorial", { 'page' : pageNumber }, function() { + block.fadeIn(); + changerUpdated(); }); + }); +} + +function goToTag(tag) { + for (i=0; i < pages.length; i++) { + if (pages[i].tag && pages[i].tag == tag) { + return goToPage(i); + } + } + return; } -function setupLink(url) { - return function(e) { $("#changer").load(url, function(data) { $("#changer").html(data); }); } +function setupLink(url, opts) { + return function(e) { $("#changer").load(url, function(data) { $("#changer").html(data);changerUpdated() }); } } function setupExamples(controller) { @@ -102,26 +71,31 @@ function html_escape(val) { } function doCommand(input) { - if (input.match(/^gopage /)) { - goToPage(parseInt(input.substring("gopage ".length))); - return true; - } - - switch (input) { - case 'next': - case 'forward': - goToPage(currentPage + 1); - return true; - case 'previous': - case 'prev': - case 'back': - goToPage(currentPage - 1); - return true; + if (input.match(/^gopage /)) { + goToPage(parseInt(input.substring("gopage ".length))); + return true; + } + + if (input.match(/^gotag /)) { + goToTag(input.substring("gotag ".length)); + return true; + } + + switch (input) { + case 'next': + case 'forward': + goToPage(currentPage + 1); + return true; + case 'previous': + case 'prev': + case 'back': + goToPage(currentPage - 1); + return true; case 'restart': case 'reset': case 'home': case 'quit': - goToPage(0); + goToPage(0); return true; default: return false; @@ -137,9 +111,9 @@ function onHandle(line, report) { // handle commands if (doCommand(input)) { - report(); - return; - } + report(); + return; + } // perform evaluation var data = eval_clojure(input); @@ -150,8 +124,8 @@ function onHandle(line, report) { } // handle page - if (currentPage >= 0 && pageExitConditions[currentPage].verify(data)) { - goToPage(currentPage + 1); + if (currentPage >= 0 && pageExitCondition(currentPage)(data)) { + goToPage(currentPage + 1); } // display expr results @@ -175,7 +149,16 @@ function changerUpdated() { var controller; +function setCurrentPage(){ + return function(){ + $.post("/tutorial?page="+currentPage, function(data){ + $("#changer").html(data); + })} +} + $(document).ready(function() { + $.getJSON("/metadata.json", function (data) { pages = data; } ); + controller = $("#console").console({ welcomeMessage:'Give me some Clojure:', promptLabel: '> ', @@ -188,6 +171,7 @@ $(document).ready(function() { $("#about").click(setupLink("about")); $("#links").click(setupLink("links")); + $("#tutorial").click( setCurrentPage() ); $("#home").click(setupLink("home")); changerUpdated(); diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html new file mode 100644 index 0000000..3ce227a --- /dev/null +++ b/resources/public/tutorial.html @@ -0,0 +1,1791 @@ +
+
+

Welcome to Clojure!

+

You can see a Clojure interpreter above - we call it a REPL.

+

Type next in the REPL to begin.

+
+
+

+ 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 + (+ 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. +

+

+ You can type next to skip + forward, back to return to the previous step, + and restart to get back to the beginning. These + commands are intercepted by the console and are not + evaluated as Clojure. +

+

+ Let's get started: type next. +

+
+
+

+ The first thing you may notice about Clojure is that common operations look... strange. +

+

+ For example, try typing (+ 3 3) in the REPL. +

+

+ 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. + (+ 3 3) is a list that contains an operator, and then the operands. + Try out the same concept with the * and - operators. +

+

+ Division might surprise you. When you're ready to move forward, try (/ 10 3). +

+
+
+

+ 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 (/ 10 3.0) to continue. +

+
+
+

Awesome!

+

+ Many Clojure functions can take an arbitrary number of arguments. + Try it out: type (+ 1 2 3 4 5 6) to continue. +

+
+
+

+ That's enough arithmetic. Let's do some fun stuff, like defining functions. + You can do that in Clojure with defn. +

+

+ Type (defn square [x] (* x x)) to define a "square" function that takes a single number and squares it. +

+
+
+

Congratulations - you just defined your first Clojure function. Many more will follow!

+

+ 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). +

+

+ Oh, sorry for talking so long - you probably want to try out your brand new function! + Type (square 10). +

+
+
+

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: +

+
+      (fn [x] (* x x))
+    
+

+ If you run this code, you'll see some cryptic output. + In Clojure, functions are just normal values like numbers or strings. + 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 + straight + away: ((fn [x] (* x x)) 10). +

+
+
+

+ 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 square or even +. The only + difference is that now you defined the function in the same place + where you called it. +

+

+ Remember 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. +

+

+ If you want, you can create a named function without + using defn; type +

+        
+          (def another-square (fn [x] (* x x)))
+        
+      
+ to continue. +

+
+
+

+ Success! Now you can call this new another-square function + just like you called the old square function: + (another-square 7) +

+

+ By now, you know that lists are quite important in Clojure. + But Clojure also has other data structures: +

+

+ Vectors: [1 2 3 4]
+ Maps: {:foo "bar" 3 4}
+ Sets: #{1 2 3 4}
+

+

+ 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 (:foo) for one of the keys, and a number for the other key. +

+

+ 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 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. +

+
+
+

+ Let's take a closer look at some of Clojure's data structures, + starting with maps: +

+
+      
+        (def m {:coffee 1.10 :tea 0.90 :chocolate 1.20})
+      
+    
+

+ We can use the get function to look things up in + our map: (get m :tea). +

+

+ Maps also act as functions - given a key as an argument, they + return the value for that key in the map: + (m :tea). +

+

+ One of reasons we use keywords as keys in maps is that they also + act as functions that look themselves up in a map! + (:tea m) +

+

+ What happens if we try to look up a key that is not present in + the map? +

+
+      
+        (get m :cappuccino)
+      
+    
+
+
+

+ We get back nil. An optional third argument + to get changes the not-found value: +

+
+      
+        (get m :cappuccino 1.50)
+      
+    
+

+ We can also use 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)
+      
+    
+

+ What happens if we try to get a value from a set? +

+
+      
+        (get #{"peas" "rice" "gravy"} "rice")
+      
+    
+

+ ...and if the element is not there? +

+
+      
+        (get #{"peas" "rice" "gravy"} "beans")
+      
+    
+
+
+

+ I told you that Clojure's data structures are immutable. Let's take + a closer look at what that means. +

+

+ When you used the 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. +

+

+ 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 + with the value 5 added no the end. Clojure uses + structural sharing to manage these immutable data + structures in a memory-efficient manner. +

+

+ Type next to continue. +

+
+
+

+ We just saw conj for adding an element to the end + of a vector. We can also use conj to add elements + to a set: +

+
+      
+        (def s #{1 2 3})
+      
+    
+
+      
+        (conj s 4)
+      
+    
+

+ We use disj to remove an element from a set: +

+
+      
+        (disj s 3)
+      
+    
+

+ Remember that these Clojure datastructures are + immutable; conj and disj return + new sets, our original s is + unchanged. +

+
+
+

+ Maps (also known as associative arrays have their own + functions for adding and removing items: + assoc and dissoc (which you can think + of associate and dissociate). +

+
+      
+        (assoc m :latte 1.80)
+      
+    
+
+      
+        (dissoc m :coffee 1.80)
+      
+    
+

+ We can assoc multiple key/value pairs in one go: +

+
+      
+        (assoc m :cappuccino 1.90 :latte 1.80)
+      
+    
+

+ If we want to assoc and dissoc in one + go, we can nest the forms: +

+
+      
+        (assoc (dissoc m :tea) :cappuccino 1.90 :latte 1.80)
+      
+    
+
+
+

+ 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.) +

+

We're going to tackle this is 3 stages:

+
    +
  1. Create a data structure to represent the sudoku puzzle and learn how to retrieve values from it
  2. +
  3. Pretty print the puzzle structure so we can see what's going on
  4. +
  5. Solve the puzzle
  6. +
+

You'll learn a lot of Clojure along the way to solving the puzzle, so enjoy the journey!

+

+ Type next to continue. +

+
+
+

+ 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: try it + (set (range 1 10)). +

+

+ You'll notice that sets are unordered. + Let's give our candidates a name; enter + (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: +

+
+      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. +

+

+ 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: + (seq "A3"). +

+
+
+

+ 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")). +

+

+ Clojure knows that the map function expects a + 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") +

+
+
+

+ We have seen 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))
+      
+    
+

+ Outside of the let, x has no meaning: + (square x). +

+

+ With this in hand, 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). +

+

+ Type next to continue. +

+
+
+

+ 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. 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? +

+
+      
+        (defn cell-index
+          [cell]
+          (let [[row col] (map str cell)
+                row-index (.indexOf "ABCDEFGHI" row)
+                col-index (.indexOf "123456789" col)]
+            ...))
+      
+    
+

+ (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. +

+
+
+

+ Did your function return the value 25? If not, evaluate the + following code to define cell-index: +

+
+      
+        (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)))
+      
+    
+

+ 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 cell "E4" 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, 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: +

+
+      
+        (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. +

+

+ 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). +

+
+
+

+ We can now define the candidates-for function: +

+
+      
+        (defn candidates-for [d]
+          (if (zero? d) candidates (hash-set d)))
+      
+    
+

+ Try it out for a few values of d: +

+
+      
+        (candidates-for 3)
+      
+    
+
+      
+        (candidates-for 0)
+      
+    

+
+
+

+ 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)))
+      
+    
+

+ Now we can define our grid: +

+
+      
+        (def grid (parse-puzzle puzzle))
+      
+    
+

+ We can look up an element in a vector using get: +

+
+      
+        (get grid (cell-index "D5"))
+      
+    
+

+ 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"))
+      
+    
+
+
+
    +
  1. Create a data structure to represent the sudoku puzzle and learn how to retrieve values from it
  2. +
  3. Pretty print the puzzle structure so we can see what's going on
  4. +
  5. Solve the puzzle
  6. +
+

+ 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 this: +

+
+      +---+---+---+
+      |53.|.7.|...|
+      |6..|195|...|
+      |.98|...|.6.|
+      +---+---+---+
+      |8..|.6.|..3|
+      |4..|8.3|..1|
+      |7..|.2.|..6|
+      +---+---+---+
+      |.6.|...|28.|
+      |...|419|..5|
+      |...|.8.|.79|
+      +---+---+---+
+    
+

+ 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
+            (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. +

+

+ Let's use for to calculate all the possible scores for two dice. +

+
+      
+        (for [x (range 1 7) y (range 1 7)] (+ x y))
+      
+    
+

+ The first argument of 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 etc. The function in + this case is to return the sum of the values. +

+

+ Type next to continue. +

+
+
+

+ The 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)
+      
+    
+

+ 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)]
+              :when (even? sum)]
+          sum)
+      
+    
+

+ For completeness, I should also tell you + about :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)
+      
+    
+
+
+

+ We want to look up in our grid all the values for a given row. + We can use 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)
+      
+    
+

+ 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))]]
+            (render-cell (get grid ix))))
+      
+    
+

+ 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 partition + 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 "D"). +

+
+
+

+ We're going to have to call (render-grid-row grid row + 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)) "ABCDEFGHI")
+      
+    
+

+ ...and, while that would be perfectly fine, we could also use + (partial render-grid-row grid) to + 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")
+      
+    
+
+
+

+ 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) "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) "ABCDEFGHI")]
+            (doseq [line (flatten [hsep (interpose hsep (partition 3 rows)) hsep])]
+              (println line))))
+      
+    
+

+ To continue, enter (render-grid grid). +

+
+ +
+
    +
  1. Create a data structure to represent the sudoku puzzle and learn how to retrieve values from it
  2. +
  3. Pretty print the puzzle structure so we can see what's going on
  4. +
  5. Solve the puzzle
  6. +
+

+ We're going to solve the puzzle using a combination + of assignment, elimination and search. + We start by finding trivially assignable cells, those that + only have a single member in the set of candidate values. We'll then + eliminate that value from the candidates of the row, column + and square of the assigned cell. Repeatedly assigning and eliminating + will be sufficient for some puzzles. For more complex ones, we'll + search by guessing through all the assignable values of the + cells until we've either reached an impossible situation or solved the + puzzle. +

+

Type next to make a start, by implementing the + elimination code.

+
+
+

When we assign the value 6 to the + 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. +

+

+ 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 [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 [cell]
+          (let [[_ col] (map str cell)]
+            (for [row "ABCDEFGHI"] (str row col))))
+      
+    
+

+ Try it out: (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"). +

+

+ 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))
+            (distinct (concat (square-peers cell)
+                              (row-peers cell)
+                              (col-peers cell)))))
+      
+    
+

+ Here, we've written a predicate function that will + return 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)))))
+      
+    
+

+ What are the peers of cell C8? +

+
+
+

+ We mentioned earlier that Clojure's 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))))
+      
+    
+

+ The 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. +

+

+ 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]
+            (if (> n 0)
+              (recur (+ accum n) (dec n))
+              accum)))
+      
+    
+

+ Try it out: (triangle-number 6). +

+
+
+

+ We can also use 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)))))
+      
+    
+

+ Try it out: (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). +

+
+
+

+ Although 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))
+      
+    
+

+ We pass reduce an accumulator function, initial value for the accumulator, + and list of things to reduce. Try out our new function: (sum [1 2 3]). +

+

+ 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 (fn [accum x] (+ accum x)) + above, but it it this case we could simply use +: +

+
+      
+        (defn sum [xs] (reduce + 0 xs))
+      
+    
+

+ The initial value for the accumulator is optional, so we can simplify this function further: +

+
+      
+        (defn sum [xs] (reduce + xs))
+      
+    
+

+ This works because +, when called with no arguments, returns 0; try it: + (+). +

+
+
+

+ OK, back to our Sudoku solver. With 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]
+          ...)
+      
+    
+

+ 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)
+                cell-value (grid ix)]
+            ...))
+      
+    
+

+ 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 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))
+      
+    
+

+ When the cell value is a set of candidates, we remove 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)))
+      
+    
+

+ Putting this all together, we have: +

+
+      
+        (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))))))
+      
+    
+

+ 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)
+      
+    
+
+
+

+ It worked! When we try to eliminate 5 as a possible value for A1, + we get an empty set, and our eliminate-one function + returns nil to indicate that we have reached an invalid state. +

+

+ 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))
+                  grid
+                  cells))
+      
+    
+

+ 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)
+      
+    
+
+
+

+ 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)
+      
+    
+

+ 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. Try this new version + of eliminate: +

+
+      
+        (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))
+      
+    
+

+ Wrapping the 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)
+      
+    
+
+
+

+ Great! That's eliminate done. +

+

+ The 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)))))
+      
+    
+

+ We're seeing 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. +

+

+ We're also seeing 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)
+      
+    
+

+ Let's try out the assign function: +

+
+      
+        (assign grid "A1" 5)
+      
+    
+

+ And an illegal assignment: +

+
+      
+        (assign grid "A2" 5)
+      
+    
+
+
+

+ We can't assign 5 to A2, as that is the only candidate for A1 so + would introduce an inconsistency. In this case, our assign + function is returning the desired nil. +

+

+ The 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... +

+

+ Of course, 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))
+      
+    
+

+ This should return false for our grid: +

+
+      
+        (solved? grid)
+      
+    
+
+
+

+ 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)
+               (= (count cell-value) 1)))
+      
+    
+

+ ...and here's how to find the first singleton in the grid: +

+
+      
+        (defn find-singleton [grid]
+          (first
+            (for [row "ABCDEFGHI"
+                  col "123456789"
+                 :let [cell (str row col)]
+                 :when (singleton? (grid (cell-index cell)))]
+              cell)))
+      
+    
+

+ What is the first singleton in our grid? +

+
+
+

+ Now we can make a start on a function for solving Sudoku puzzles! +

+
+      
+        (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)))
+      
+    
+

+ We're seeing 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. +

+

+ This next command may take a few seconds to run... +

+
+      
+        (render-grid (solve grid))
+      
+    
+
+
+

+ 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
+                                      000048506
+                                      040000600
+                                      780020091
+                                      001000030
+                                      109870000
+                                      200000007
+                                      000209100"))
+      
+    
+

+ Try solving this grid: + (render-grid (solve hard-grid)). + (Again, it may take a few seconds.) +

+
+
+

+ 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 solve + function so far: +

+
+      
+        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)))
+      
+    
+

+ 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 next to continue. +

+
+
+

+ 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
+            (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)))
+      
+    
+

+ Try it out: (find-unsolved hard-grid). +

+
+
+

+ We can now add search to our 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)))))))
+      
+    
+

+ Phew! There's quite a lot going on in there. We use 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. +

+

+ Let's try it (but be patient, it may take a few seconds): +

+
+      
+        (render-grid (solve hard-grid))
+      
+    
+
+
+

+ 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 below for suggested further reading. +

+
diff --git a/resources/public/tutorial/end.html b/resources/public/tutorial/end.html deleted file mode 100644 index f85f040..0000000 --- a/resources/public/tutorial/end.html +++ /dev/null @@ -1,7 +0,0 @@ -

- 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? -

diff --git a/resources/public/tutorial/page1.html b/resources/public/tutorial/page1.html deleted file mode 100644 index 4654fcb..0000000 --- a/resources/public/tutorial/page1.html +++ /dev/null @@ -1,7 +0,0 @@ -

- 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 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. -

diff --git a/resources/public/tutorial/page10.html b/resources/public/tutorial/page10.html deleted file mode 100644 index f1e2228..0000000 --- a/resources/public/tutorial/page10.html +++ /dev/null @@ -1,35 +0,0 @@ -

- Success! Now you can call this new square function just like you called the old square function. -

- -

- By now, you know that lists are quite important in Clojure. - But Clojure also has other data structures: -

- -

- Vectors: [1 2 3 4]
- Maps: {:foo "bar" 3 4}
- Sets: #{1 2 3 4}
-

- -

- 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 (:foo) for one of the keys, and a number for the other key. -

- -

- 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 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. -

diff --git a/resources/public/tutorial/page11.html b/resources/public/tutorial/page11.html deleted file mode 100644 index 726fe3c..0000000 --- a/resources/public/tutorial/page11.html +++ /dev/null @@ -1,11 +0,0 @@ -

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! -

diff --git a/resources/public/tutorial/page2.html b/resources/public/tutorial/page2.html deleted file mode 100644 index 1d11c62..0000000 --- a/resources/public/tutorial/page2.html +++ /dev/null @@ -1,7 +0,0 @@ -

- The first thing you may notice about Clojure is that common operations look... strange. -

- -

- For example, try typing (+ 3 3) in the REPL. -

diff --git a/resources/public/tutorial/page3.html b/resources/public/tutorial/page3.html deleted file mode 100644 index ae8cc9b..0000000 --- a/resources/public/tutorial/page3.html +++ /dev/null @@ -1,13 +0,0 @@ -

- That was a strange way to say "three plus three", wasn't it? -

- -

- A Clojure program is made of lists. - (+ 3 3) is a list that contains an operator, and then the operands. - Try out the same concept with the * and - operators. -

- -

- Division might surprise you. When you're ready to move forward, try (/ 10 3). -

diff --git a/resources/public/tutorial/page4.html b/resources/public/tutorial/page4.html deleted file mode 100644 index 991dc20..0000000 --- a/resources/public/tutorial/page4.html +++ /dev/null @@ -1,4 +0,0 @@ -

- 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 (/ 10 3.0) to continue. -

diff --git a/resources/public/tutorial/page5.html b/resources/public/tutorial/page5.html deleted file mode 100644 index 1788c09..0000000 --- a/resources/public/tutorial/page5.html +++ /dev/null @@ -1,6 +0,0 @@ -

Awesome!

- -

- Many Clojure functions can take an arbitrary number of arguments. - Try it out: type (+ 1 2 3 4 5 6) to continue. -

diff --git a/resources/public/tutorial/page6.html b/resources/public/tutorial/page6.html deleted file mode 100644 index daa7698..0000000 --- a/resources/public/tutorial/page6.html +++ /dev/null @@ -1,8 +0,0 @@ -

- That's enough math. Let's do some fun stuff, like defining functions. - You can do that in Clojure with defn. -

- -

- Type (defn square [x] (* x x)) to define a "square" function that takes a single number and squares it. -

diff --git a/resources/public/tutorial/page7.html b/resources/public/tutorial/page7.html deleted file mode 100644 index e6c58c0..0000000 --- a/resources/public/tutorial/page7.html +++ /dev/null @@ -1,13 +0,0 @@ -

Congratulations - you just defined your first Clojure function. Many more will follow!

- -

- 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). -

- -

- Oh, sorry for talking so long - you probably want to try out your brand new function! - Type (square 10). -

diff --git a/resources/public/tutorial/page8.html b/resources/public/tutorial/page8.html deleted file mode 100644 index d3a26cf..0000000 --- a/resources/public/tutorial/page8.html +++ /dev/null @@ -1,20 +0,0 @@ -

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: -

- -(fn [x] (* x x)) - -

- If you run this code, you'll see some cryptic output. - In Clojure, functions are just normal values like numbers or strings. - 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 straight away: ((fn [x] (* x x)) 10). -

diff --git a/resources/public/tutorial/page9.html b/resources/public/tutorial/page9.html deleted file mode 100644 index 8679f37..0000000 --- a/resources/public/tutorial/page9.html +++ /dev/null @@ -1,16 +0,0 @@ -

- 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 square or even +. - The only difference is that now you defined the function in the same place where you called it. -

- -

- Remember 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. -

- -

- If you want, you can create a named functions without using defn: type (def square (fn [x] (* x x))) to continue. -

diff --git a/src/tryclojure/models/eval.clj b/src/tryclojure/models/eval.clj index 810f862..9ca1161 100644 --- a/src/tryclojure/models/eval.clj +++ b/src/tryclojure/models/eval.clj @@ -21,10 +21,9 @@ (defn make-sandbox [] (sandbox try-clojure-tester - :timeout 2000 - :init '(do (require '[clojure.repl :refer [doc source]]) - (future (Thread/sleep 600000) - (-> *ns* .getName remove-ns))))) + :max-defs 200 + :timeout 1000000 + :init '(do (require '[clojure.repl :refer [doc source]])))) (defn find-sb [old] (if-let [sb (get old "sb")] diff --git a/src/tryclojure/models/tutorial.clj b/src/tryclojure/models/tutorial.clj new file mode 100644 index 0000000..263a563 --- /dev/null +++ b/src/tryclojure/models/tutorial.clj @@ -0,0 +1,18 @@ +(ns tryclojure.models.tutorial + (:require [clojure.java.io :as io] + [net.cgrand.enlive-html :as html])) + +(def tutorial-resource (io/resource "public/tutorial.html")) + +(defn get-metadata + [] + (let [tutorial (html/html-resource tutorial-resource)] + (map (fn [{:keys [attrs]}] + (into {} (for [k (keys attrs) :when (.startsWith (name k) "data-")] + [(keyword (subs (name k) 5)) (attrs k)]))) + (html/select tutorial [:div.page])))) + +(defn get-page + [n] + (let [tutorial (html/html-resource tutorial-resource)] + (nth (html/select tutorial [:div.page]) n))) diff --git a/src/tryclojure/server.clj b/src/tryclojure/server.clj index 010ce2f..2da4a95 100644 --- a/src/tryclojure/server.clj +++ b/src/tryclojure/server.clj @@ -2,22 +2,52 @@ (:use compojure.core) (:require [compojure.route :as route] [noir.util.middleware :as nm] + [noir.session :as session] + [clojure.tools.logging :as log] [ring.adapter.jetty :as jetty] [tryclojure.views.home :as home] [tryclojure.views.tutorial :as tutorial] - [tryclojure.views.eval :as eval])) + [tryclojure.views.eval :as eval]) + (:import java.util.UUID)) + +(defn wrap-session-id + "Middleware to assign a UUID for tracking page views. The main app + does not create a cookie until the first attempt to evaluate Clojure + code in the REPL. This middleware assures a UUID and session cookie + are assigned on the first page view." + [handler] + (fn [request] + (session/swap! (fn [sess] + (if (get sess :uuid) + sess + (assoc sess :uuid (.toString (UUID/randomUUID)))))) + (handler request))) + +(defn wrap-log-request + [handler] + (fn [request] + (log/debug request) + (handler request))) + +(defn wrap-log-pageview + [handler] + (fn [request] + (when (= (:uri request) "/tutorial") + (log/info "PAGEVIEW" (session/get :uuid) (get-in request [:params :page]))) + (handler request))) (def app-routes [(GET "/" [] (home/root-html)) (GET "/about" [] (home/about-html)) (GET "/links" [] (home/links-html)) + (GET "/metadata.json" [] (tutorial/tutorial-meta)) (POST "/tutorial" [:as {args :params}] (tutorial/tutorial-html (args :page))) (POST "/eval.json" [:as {args :params}] (eval/eval-json (args :expr) (args :jsonp))) (GET "/eval.json" [:as {args :params}] (eval/eval-json (args :expr) (args :jsonp))) (route/resources "/") (route/not-found "Not Found")]) -(def app (nm/app-handler app-routes)) +(def app (nm/app-handler app-routes :middleware [wrap-session-id wrap-log-request wrap-log-pageview])) (defn -main [port] (jetty/run-jetty app {:port (Long. port) :join? false})) diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj index e5967b0..3ded457 100644 --- a/src/tryclojure/views/home.clj +++ b/src/tryclojure/views/home.clj @@ -8,9 +8,11 @@ (unordered-list [(link-to "http://clojure.org" "The official Clojure website") (link-to "http://clojure-doc.org/" "Clojure tutorials and documentation") + (link-to "http://jafingerhut.github.io/cheatsheet-clj-1.3/cheatsheet-tiptip-no-cdocs-summary.html" "Clojure Cheat Sheet") (link-to "http://groups.google.com/group/clojure" "Clojure mailing list") + (link-to "http://www.clojurebook.com/" "Clojue Programming: a book by Chas Emerick, Brian Carper and Christophe Grand") (link-to "http://joyofclojure.com/" "The Joy of Clojure: a book by Michael Fogus and Chris Houser") - (link-to "http://disclojure.org" "Disclojure") + (link-to "http://clojure-cookbook.com/" "The Clojure Cookbook: a book by Luke VanderHart and Ryan Neufeld, with community contributions") (link-to "http://planet.clojure.in" "Planet Clojure")]))) (defn about-html [] @@ -20,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") ", " @@ -30,11 +32,22 @@ " The design is by " (link-to "http://apgwoz.com" "Andrew Gwozdziewycz") "."])) (defn home-html [] - (html - [: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." ])) + (html + [:p.bottom "Welcome to Clojure! We are:"] + [:ul + [:li "Ray Miller @ray1729"] + [:li "Jim Downing @jimdowning"] + [:li "Gareth Rogers"]] + [:p.bottom + "Please grab any of us if you have questions through this session or afterwards."] + [:p.bottom + "If you want to learn more clojure after today, consider joining us at the + monthly Cambridge Clojure meetup: bit.ly/camclj"] + [:p.bottom + "To get started, go to bit.ly/tryclj"] + [:p.bottom + "You can see a Clojure interpreter above - we call it a REPL." + "Type next in the REPL to begin." ])) (defn root-html [] (html5 @@ -49,7 +62,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 @@ -57,20 +70,10 @@ [:span.logo-clojure "Clo" [:em "j"] "ure"]]] [:div#container [:div#console.console] + [:div#changer (home-html)] [:div#buttons + [:a#tutorial.buttons "tutorial"] [:a#links.buttons "links"] - [:a#about.buttons.last "about"]] - [:div#changer (home-html)]] + [:a#about.buttons.last "about"]]] [: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."]]]]])) diff --git a/src/tryclojure/views/tutorial.clj b/src/tryclojure/views/tutorial.clj index 901fe52..b498f03 100644 --- a/src/tryclojure/views/tutorial.clj +++ b/src/tryclojure/views/tutorial.clj @@ -1,4 +1,48 @@ -(ns tryclojure.views.tutorial) +(ns tryclojure.views.tutorial + (:require [tryclojure.models.tutorial :as m] + [clojure.string :as str] + [net.cgrand.enlive-html :as html] + [noir.response :as response])) -(defn tutorial-html [page] - (slurp (str "resources/public/tutorial/" page ".html"))) \ No newline at end of file +(defn- try-parse-int + [n] + (try + (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 (format-code-blocks content))))))) + +(defn tutorial-meta [] + (when-let [metadata (m/get-metadata)] + (response/json metadata))) diff --git a/src/tutorial/examples/sudoku.clj b/src/tutorial/examples/sudoku.clj new file mode 100644 index 0000000..327099f --- /dev/null +++ b/src/tutorial/examples/sudoku.clj @@ -0,0 +1,153 @@ +(ns tutorial.examples.sudoku) + +(def candidates (set (range 1 10))) + +(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)))) + +(def puzzle "530070000 + 600195000 + 098000060 + 800060003 + 400803001 + 700020006 + 060000280 + 000419005 + 000080079") + +(defn parse-int [s] (Integer/parseInt s)) + +(defn candidates-for [d] + (if (zero? d) candidates (hash-set d))) + +(defn parse-puzzle [puzzle] + (mapv (comp candidates-for parse-int) + (re-seq #"\d" puzzle))) + +(def grid (parse-puzzle puzzle)) + +(defn render-cell [x] + (cond + (number? x) x + (= (count x) 1) (first x) + :else ".")) + +(defn row-render-values [grid row] + (for [col (range 1 10) :let [cell (str row col)]] + (render-cell (get grid (cell-index cell))))) + +(defn render-grid-row [grid row] + (apply str + (flatten + ["|" (interpose "|" (partition 3 (row-render-values grid row))) "|"]))) + +(defn render-grid [grid] + (let [hsep "+---+---+---+" + rows (map (partial render-grid-row grid) "ABCDEFGHI")] + (doseq [line (flatten [hsep (interpose hsep (partition 3 rows)) hsep])] + (println line)))) + +(defn row-peers [cell] + (let [[row _] (map str cell)] + (for [col "123456789"] (str row col)))) + +(defn col-peers [cell] + (let [[_ col] (map str cell)] + (for [row "ABCDEFGHI"] (str row col)))) + +(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)))) + +(defn peers [cell] + (remove #{cell} (distinct (concat (square-peers cell) + (row-peers cell) + (col-peers cell))))) + +(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)))))) + +(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)) + +(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))))) + +(defn solved? [grid] + (every? number? 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))) + +(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))) + +(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 (filter identity + (map (fn [candidate] + (solve (assign grid cell candidate))) + candidates)))))))