From d7c6c78f701f7f17a6c89dcdfd3f0a1b0a2714df Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sun, 29 Jun 2014 11:54:51 +0100 Subject: [PATCH 01/37] Refactor to define tutorial in single HTML page and generate metadata from data- attributes rather than modifying JavaScript when pages are added. --- .gitignore | 2 + project.clj | 1 + resources/public/javascript/tryclojure.js | 156 +++++++---------- resources/public/tutorial.html | 202 ++++++++++++++++++++++ resources/public/tutorial/end.html | 7 - resources/public/tutorial/page1.html | 7 - resources/public/tutorial/page10.html | 35 ---- resources/public/tutorial/page11.html | 11 -- resources/public/tutorial/page2.html | 7 - resources/public/tutorial/page3.html | 13 -- resources/public/tutorial/page4.html | 4 - resources/public/tutorial/page5.html | 6 - resources/public/tutorial/page6.html | 8 - resources/public/tutorial/page7.html | 13 -- resources/public/tutorial/page8.html | 20 --- resources/public/tutorial/page9.html | 16 -- src/tryclojure/models/tutorial.clj | 18 ++ src/tryclojure/server.clj | 1 + src/tryclojure/views/tutorial.clj | 21 ++- 19 files changed, 308 insertions(+), 240 deletions(-) create mode 100644 resources/public/tutorial.html delete mode 100644 resources/public/tutorial/end.html delete mode 100644 resources/public/tutorial/page1.html delete mode 100644 resources/public/tutorial/page10.html delete mode 100644 resources/public/tutorial/page11.html delete mode 100644 resources/public/tutorial/page2.html delete mode 100644 resources/public/tutorial/page3.html delete mode 100644 resources/public/tutorial/page4.html delete mode 100644 resources/public/tutorial/page5.html delete mode 100644 resources/public/tutorial/page6.html delete mode 100644 resources/public/tutorial/page7.html delete mode 100644 resources/public/tutorial/page8.html delete mode 100644 resources/public/tutorial/page9.html create mode 100644 src/tryclojure/models/tutorial.clj diff --git a/.gitignore b/.gitignore index 102c661..7cb5457 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ lib classes *~ .lein-deps-sum +target/ +.nrepl-port diff --git a/project.clj b/project.clj index 44808a1..1b03015 100644 --- a/project.clj +++ b/project.clj @@ -1,6 +1,7 @@ (defproject tryclojure "0.1.0-SNAPSHOT" :description "A simple web-based Clojure REPL for trying out Clojure without having to install it." :dependencies [[org.clojure/clojure "1.4.0"] + [enlive "1.1.5"] [lib-noir "0.8.1"] [compojure "1.1.6"] [ring-server "0.3.1"] diff --git a/resources/public/javascript/tryclojure.js b/resources/public/javascript/tryclojure.js index c6069fc..a292741 100644 --- a/resources/public/javascript/tryclojure.js +++ b/resources/public/javascript/tryclojure.js @@ -1,71 +1,40 @@ +var pages; + var currentPage = -1; -var pages = [ - "page1", - "page2", - "page3", - "page4", - "page5", - "page6", - "page7", - "page8", - "page9", - "page10", - "page11", - "end" - ]; -var pageExitConditions = [ - { - verify: function(data) { return false; } - }, - { - verify: function(data) { return data.expr == "(+ 3 3)"; } - }, - { - verify: function(data) { return data.expr == "(/ 10 3)"; } - }, - { - verify: function(data) { return data.expr == "(/ 10 3.0)"; } - }, - { - verify: function(data) { return data.expr == "(+ 1 2 3 4 5 6)"; } - }, - { - verify: function (data) { return data.expr == "(defn square [x] (* x x))"; } - }, - { - verify: function (data) { return data.expr == "(square 10)"; } - }, - { - verify: function (data) { return data.expr == "((fn [x] (* x x)) 10)"; } - }, - { - verify: function (data) { return data.expr == "(def square (fn [x] (* x x)))"; } - }, - { - verify: function (data) { return data.expr == "(map inc [1 2 3 4])"; } - }, - { - verify: function (data) { return false; } - }, - { - verify: function (data) { return false; } + +function pageExitCondition(pageNumber) { + if (pageNumber >= 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) { @@ -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; @@ -134,26 +108,26 @@ function onValidate(input) { function onHandle(line, report) { var input = $.trim(line); - + // handle commands if (doCommand(input)) { - report(); - return; - } + report(); + return; + } // perform evaluation var data = eval_clojure(input); - + // handle error if (data.error) { return [{msg: data.message, className: "jquery-console-message-error"}]; } - + // handle page - if (currentPage >= 0 && pageExitConditions[currentPage].verify(data)) { - goToPage(currentPage + 1); + if (currentPage >= 0 && pageExitCondition(currentPage)(data)) { + goToPage(currentPage + 1); } - + // display expr results return [{msg: data.result, className: "jquery-console-message-value"}]; } @@ -176,6 +150,8 @@ function changerUpdated() { var controller; $(document).ready(function() { + $.getJSON("/metadata.json", function (data) { pages = data; } ); + controller = $("#console").console({ welcomeMessage:'Give me some Clojure:', promptLabel: '> ', @@ -185,10 +161,10 @@ $(document).ready(function() { animateScroll:true, promptHistory:true }); - + $("#about").click(setupLink("about")); $("#links").click(setupLink("links")); $("#home").click(setupLink("home")); - + changerUpdated(); }); diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html new file mode 100644 index 0000000..53d13d5 --- /dev/null +++ b/resources/public/tutorial.html @@ -0,0 +1,202 @@ +
+
+

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

+
+
+

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

+
+
+

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 functions without + using defn: type + (def square (fn [x] (* x x))) to continue. +

+
+
+

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

+
+
+

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

+
+
+

+ 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/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/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..062d0a4 100644 --- a/src/tryclojure/server.clj +++ b/src/tryclojure/server.clj @@ -11,6 +11,7 @@ [(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))) diff --git a/src/tryclojure/views/tutorial.clj b/src/tryclojure/views/tutorial.clj index 901fe52..7fd21d6 100644 --- a/src/tryclojure/views/tutorial.clj +++ b/src/tryclojure/views/tutorial.clj @@ -1,4 +1,19 @@ -(ns tryclojure.views.tutorial) +(ns tryclojure.views.tutorial + (:require [tryclojure.models.tutorial :as m] + [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 tutorial-html [page] + (when-let [n (Integer/parseInt page)] + (when-let [content (m/get-page n)] + (apply str (html/emit* (html/unwrap content)))))) + +(defn tutorial-meta [] + (when-let [metadata (m/get-metadata)] + (response/json metadata))) From 70a58b74927840ebeb0d70e7f4ad70d5f57e931e Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sun, 29 Jun 2014 17:40:44 +0100 Subject: [PATCH 02/37] Increase max-defs limit on the sandbox. --- src/tryclojure/models/eval.clj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tryclojure/models/eval.clj b/src/tryclojure/models/eval.clj index 810f862..03c2e97 100644 --- a/src/tryclojure/models/eval.clj +++ b/src/tryclojure/models/eval.clj @@ -21,10 +21,9 @@ (defn make-sandbox [] (sandbox try-clojure-tester + :max-defs 200 :timeout 2000 - :init '(do (require '[clojure.repl :refer [doc source]]) - (future (Thread/sleep 600000) - (-> *ns* .getName remove-ns))))) + :init '(do (require '[clojure.repl :refer [doc source]])))) (defn find-sb [old] (if-let [sb (get old "sb")] From edd4e3ab61089ce0e9b88450ac77a003217fd79a Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sun, 29 Jun 2014 17:40:59 +0100 Subject: [PATCH 03/37] Increase width. --- resources/public/css/tryclojure.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index 4a355d1..cbd503a 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -5,7 +5,7 @@ body { #wrapper { text-align: left; - width: 620px; + width: 820px; margin: 0 auto; border-radius: 5px; border: 5px solid #eee; @@ -49,7 +49,7 @@ body { } #console div.jquery-console-inner { - width:580px; + width:780px; height:200px; margin: 10px 10px; overflow:auto; From 3c4d1808c3ea70b03e487568da445fab62a0ba35 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sun, 29 Jun 2014 17:41:16 +0100 Subject: [PATCH 04/37] More content. --- resources/public/tutorial.html | 461 ++++++++++++++++++++++++++++++++- 1 file changed, 459 insertions(+), 2 deletions(-) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 53d13d5..3cecf2e 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -4,7 +4,10 @@ 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. + 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.

@@ -190,12 +193,466 @@ Type 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?

From 4833184e7020a473ed188f80d5f408bd50d6a661 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Tue, 1 Jul 2014 15:05:03 +0100 Subject: [PATCH 05/37] CSS tweaks --- resources/public/css/tryclojure.css | 55 ++++++++++++--------- resources/public/tutorial.html | 74 +++++++++++------------------ 2 files changed, 61 insertions(+), 68 deletions(-) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index cbd503a..d7d54f9 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -48,43 +48,43 @@ body { border: 1px solid #aaa; } -#console div.jquery-console-inner { +#console div.jquery-console-inner { width:780px; - height:200px; + height:200px; margin: 10px 10px; - overflow:auto; + overflow:auto; text-align:left; } #console div.jquery-console-message-value { - color:#0066FF; + color:#0066FF; font-family:monospace; - padding:0.1em; + padding:0.1em; } #console div.jquery-console-prompt-box { - color:#444; font-family:monospace; + color:#444; font-family:monospace; } #console div.jquery-console-focus span.jquery-console-cursor { background:#333; color:#eee; font-weight:bold; } #console div.jquery-console-message-error { color:#ef0505; font-family:sans-serif; font-weight:bold; - padding:0.1em; + padding:0.1em; } #console div.jquery-console-message-success { color:#187718; font-family:monospace; - padding:0.1em; + padding:0.1em; } -#console span.jquery-console-prompt-label { +#console span.jquery-console-prompt-label { font-weight:bold; } -.bottom { - background-color: white; color: #333; +.bottom { + background-color: white; color: #333; } -.bottom a,a:visited { - color: #111; +.bottom a,a:visited { + color: #111; } -table.bottom { +table.bottom { width: 100%; border: 1px solid black; } @@ -93,13 +93,13 @@ table.bottom { text-align: center; margin-top: 10px; } -#buttons a { +#buttons a { /* color: #4881d8;*/ color: #3D5B99; padding: .3em 1em; margin-right: .7em; font-family: Helvetica, sans-serif; - font-size: 16px; + font-size: 16px; font-weight: bold; background: #90b4fe; border-radius: 5px; @@ -110,10 +110,10 @@ table.bottom { } #buttons a:hover { color: #5881d8; - cursor: pointer; + cursor: pointer; } -#changer { +#changer { margin: 10px; padding: 0.25em 0.5em 0.25em 0.5em; background: #EAF2F5; @@ -148,11 +148,22 @@ div#tuttext { div.continue { width: 100%; text-align: center; - padding-top: 0.5em; + padding-top: 0.5em; +} + +div.footer { + text-align: center; } -div.footer { - text-align: center; +pre.codeblock { + background: #f0f0f0; + border: 1px solid #aaa; + padding: 10px 0; +} + +#changer pre.codeblock code{ + background-color: transparent; + border: none; } /* Coderay alpha style */ @@ -204,7 +215,7 @@ table.code td { padding: 2px 4px; vertical-align: top; } /* operator */ #changer .code span.cl { - + } /* number */ diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 3cecf2e..e34a850 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -87,7 +87,7 @@ 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 @@ -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.

@@ -177,11 +178,11 @@

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

@@ -623,7 +606,7 @@ Let's define 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 @@

- From 488e227b93df01399767883cdb990238c0d32914 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Tue, 8 Jul 2014 17:34:03 +0100 Subject: [PATCH 06/37] Strip whitespace around code blocks; add more content. --- resources/public/javascript/tryclojure.js | 18 +- resources/public/tutorial.html | 335 ++++++++++++++++------ src/tryclojure/views/tutorial.clj | 31 +- 3 files changed, 288 insertions(+), 96 deletions(-) diff --git a/resources/public/javascript/tryclojure.js b/resources/public/javascript/tryclojure.js index a292741..4601972 100644 --- a/resources/public/javascript/tryclojure.js +++ b/resources/public/javascript/tryclojure.js @@ -18,7 +18,7 @@ function goToPage(pageNumber) { } currentPage = pageNumber; - + var block = $("#changer"); block.fadeOut(function(e) { block.load("/tutorial", { 'page' : pageNumber }, function() { @@ -80,7 +80,7 @@ function doCommand(input) { goToTag(input.substring("gotag ".length)); return true; } - + switch (input) { case 'next': case 'forward': @@ -108,7 +108,7 @@ function onValidate(input) { function onHandle(line, report) { var input = $.trim(line); - + // handle commands if (doCommand(input)) { report(); @@ -117,17 +117,17 @@ function onHandle(line, report) { // perform evaluation var data = eval_clojure(input); - + // handle error if (data.error) { return [{msg: data.message, className: "jquery-console-message-error"}]; } - + // handle page if (currentPage >= 0 && pageExitCondition(currentPage)(data)) { goToPage(currentPage + 1); } - + // display expr results return [{msg: data.result, className: "jquery-console-message-value"}]; } @@ -151,7 +151,7 @@ var controller; $(document).ready(function() { $.getJSON("/metadata.json", function (data) { pages = data; } ); - + controller = $("#console").console({ welcomeMessage:'Give me some Clojure:', promptLabel: '> ', @@ -161,10 +161,10 @@ $(document).ready(function() { animateScroll:true, promptHistory:true }); - + $("#about").click(setupLink("about")); $("#links").click(setupLink("links")); $("#home").click(setupLink("home")); - + changerUpdated(); }); diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index e34a850..b391d93 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -114,8 +114,12 @@

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.

@@ -292,41 +402,51 @@ 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 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:

@@ -394,7 +524,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)
@@ -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?

@@ -418,7 +548,7 @@ +---+---+---+ |53.|.7.|...| |6..|195|...| - |.98|...|..6| + |.98|...|.6.| +---+---+---+ |8..|.6.|..3| |4..|8.3|..1| @@ -430,10 +560,10 @@ +---+---+---+

- 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 to apply @@ -510,52 +664,58 @@ 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))) "|"])))
+
+      
+        (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 like render-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 like render-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 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])
+        (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.

@@ -439,7 +448,8 @@

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

@@ -748,12 +773,13 @@ 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 + 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). -

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

From a295c531465acd7494a01d4e63f5396089ff954b Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Tue, 8 Jul 2014 19:44:06 +0100 Subject: [PATCH 08/37] Update links --- src/tryclojure/views/home.clj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj index e5967b0..baa6690 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 [] @@ -30,7 +32,7 @@ " The design is by " (link-to "http://apgwoz.com" "Andrew Gwozdziewycz") "."])) (defn home-html [] - (html + (html [:p.bottom "Welcome to Clojure! " "You can see a Clojure interpreter above - we call it a REPL."] @@ -73,4 +75,3 @@ 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); })();")]]])) - From 791e732bf5c3e348391010c1e1b8df89fe00921c Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Wed, 9 Jul 2014 19:23:46 +0100 Subject: [PATCH 09/37] Almost done... --- project.clj | 5 +- resources/public/tutorial.html | 623 +++++++++++++++++++++++++++++-- src/tryclojure/models/eval.clj | 2 +- src/tutorial/examples/sudoku.clj | 153 ++++++++ 4 files changed, 759 insertions(+), 24 deletions(-) create mode 100644 src/tutorial/examples/sudoku.clj diff --git a/project.clj b/project.clj index 1b03015..24b15f6 100644 --- a/project.clj +++ b/project.clj @@ -1,6 +1,6 @@ (defproject tryclojure "0.1.0-SNAPSHOT" :description "A simple web-based Clojure REPL for trying out Clojure without having to install it." - :dependencies [[org.clojure/clojure "1.4.0"] + :dependencies [[org.clojure/clojure "1.6.0"] [enlive "1.1.5"] [lib-noir "0.8.1"] [compojure "1.1.6"] @@ -11,4 +11,5 @@ :min-lein-version "2.0.0" :uberjar-name "tryclojure-standalone.jar" :plugins [[lein-ring "0.8.10"]] - :ring {:handler tryclojure.server/app :port 8801}) + :ring {:handler tryclojure.server/app :port 8801} + :profiles {:dev {:ring {:nrepl? true}}}) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index eb92f25..772a4f2 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -438,9 +438,14 @@

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

- (Note the different syntax for calling a static method: the class name, - followed by a /, then the method name.) With that in hand, try:

@@ -491,7 +494,10 @@
       (hash-set 3).
     

-
+
+

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

       
         (defn candidates-for [d]
@@ -499,9 +505,17 @@
       
     

- Try it out for a few values of 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)
+      
     

@@ -526,13 +540,15 @@
-
+

We're almost there! Let's look at the data type of grid:

- - (type grid) - +
+      
+        (type grid)
+      
+    

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:

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

- What are the candidates for the cell C8? + 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"))
+      
+    

@@ -851,7 +885,7 @@ 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: @@ -867,19 +901,566 @@

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

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

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

+
+      
+        (eliminate grid ["A1" "A2" "A3" "A4" "A5"] 5)
+      
+    

- Maybe you can click the 'links' button below for more resources? + 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:

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

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

+
+
+

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

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

diff --git a/src/tryclojure/models/eval.clj b/src/tryclojure/models/eval.clj index 03c2e97..9ca1161 100644 --- a/src/tryclojure/models/eval.clj +++ b/src/tryclojure/models/eval.clj @@ -22,7 +22,7 @@ (defn make-sandbox [] (sandbox try-clojure-tester :max-defs 200 - :timeout 2000 + :timeout 1000000 :init '(do (require '[clojure.repl :refer [doc source]])))) (defn find-sb [old] 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))))))) From e6e12a4da11ef876fba77fe7fe8a173ff6f0bb08 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Wed, 9 Jul 2014 19:44:27 +0100 Subject: [PATCH 10/37] Minor tweaks --- resources/public/tutorial.html | 4 ++-- src/tryclojure/views/home.clj | 16 +++------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 772a4f2..54ecf43 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -52,7 +52,7 @@

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

@@ -188,7 +188,7 @@ vector: try (conj v 5).

- Now examine 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 Date: Wed, 9 Jul 2014 21:12:53 +0100 Subject: [PATCH 11/37] Bump dependency versions; minor corrections to tutorial text. --- project.clj | 6 +++--- resources/public/tutorial.html | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/project.clj b/project.clj index 24b15f6..fb675a0 100644 --- a/project.clj +++ b/project.clj @@ -2,14 +2,14 @@ :description "A simple web-based Clojure REPL for trying out Clojure without having to install it." :dependencies [[org.clojure/clojure "1.6.0"] [enlive "1.1.5"] - [lib-noir "0.8.1"] - [compojure "1.1.6"] + [lib-noir "0.8.4"] + [compojure "1.1.8"] [ring-server "0.3.1"] [commons-lang/commons-lang "2.5"] [clojail "1.0.6"]] :jvm-opts ["-Djava.security.policy=example.policy" "-Xmx80M"] :min-lein-version "2.0.0" :uberjar-name "tryclojure-standalone.jar" - :plugins [[lein-ring "0.8.10"]] + :plugins [[lein-ring "0.8.11"]] :ring {:handler tryclojure.server/app :port 8801} :profiles {:dev {:ring {:nrepl? true}}}) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 54ecf43..f2e0e08 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -113,7 +113,7 @@ def binds the newly defined function to a name.

- If you want, you can create a named functions without + If you want, you can create a named function without using 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 @@
   

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:

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

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

@@ -1371,7 +1374,8 @@

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: + 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 
Date: Wed, 9 Jul 2014 22:04:36 +0100
Subject: [PATCH 12/37] Log page views.

---
 project.clj                |  2 ++
 resources/log4j.properties | 10 ++++++++++
 src/tryclojure/server.clj  | 33 +++++++++++++++++++++++++++++++--
 3 files changed, 43 insertions(+), 2 deletions(-)
 create mode 100644 resources/log4j.properties

diff --git a/project.clj b/project.clj
index fb675a0..9be9c8a 100644
--- a/project.clj
+++ b/project.clj
@@ -1,6 +1,8 @@
 (defproject tryclojure "0.1.0-SNAPSHOT"
   :description "A simple web-based Clojure REPL for trying out Clojure without having to install it."
   :dependencies [[org.clojure/clojure "1.6.0"]
+                 [org.clojure/tools.logging "0.3.0"]
+                 [log4j/log4j "1.2.17"]
                  [enlive "1.1.5"]
                  [lib-noir "0.8.4"]
                  [compojure "1.1.8"]
diff --git a/resources/log4j.properties b/resources/log4j.properties
new file mode 100644
index 0000000..a9d4dae
--- /dev/null
+++ b/resources/log4j.properties
@@ -0,0 +1,10 @@
+log4j.rootLogger=INFO, console, file
+log4j.logger.example=DEBUG
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-5p %c: %m%n
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.File=server.log
+log4j.appender.file.Append=true
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d %p %m%n
\ No newline at end of file
diff --git a/src/tryclojure/server.clj b/src/tryclojure/server.clj
index 062d0a4..2da4a95 100644
--- a/src/tryclojure/server.clj
+++ b/src/tryclojure/server.clj
@@ -2,10 +2,39 @@
   (: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))
@@ -18,7 +47,7 @@
    (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}))

From 3a4d023faa00e02b215a433d1af680324e5c1296 Mon Sep 17 00:00:00 2001
From: Ray Miller 
Date: Thu, 10 Jul 2014 12:44:10 +0100
Subject: [PATCH 13/37] Store logs in subdir.

---
 .gitignore                 | 1 +
 resources/log4j.properties | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore
index 7cb5457..31556e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ classes
 .lein-deps-sum
 target/
 .nrepl-port
+logs/
diff --git a/resources/log4j.properties b/resources/log4j.properties
index a9d4dae..30ef032 100644
--- a/resources/log4j.properties
+++ b/resources/log4j.properties
@@ -4,7 +4,7 @@ log4j.appender.console=org.apache.log4j.ConsoleAppender
 log4j.appender.console.layout=org.apache.log4j.PatternLayout
 log4j.appender.console.layout.ConversionPattern=%-5p %c: %m%n
 log4j.appender.file=org.apache.log4j.FileAppender
-log4j.appender.file.File=server.log
+log4j.appender.file.File=logs/server.log
 log4j.appender.file.Append=true
 log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d %p %m%n
\ No newline at end of file
+log4j.appender.file.layout.ConversionPattern=%d %p %m%n

From 734146097724615a76e62621002ef97901d1eb1d Mon Sep 17 00:00:00 2001
From: Ray Miller 
Date: Thu, 10 Jul 2014 13:12:08 +0100
Subject: [PATCH 14/37] EC2 setup notes added to README

---
 README.md | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/README.md b/README.md
index 65cfeb7..648c642 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,40 @@ http://tryclj.com
 
 To run it locally, use `lein ring server`.
 
+## Amazon EC2 setup
+
+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' <
Date: Thu, 10 Jul 2014 19:00:44 +0100
Subject: [PATCH 15/37] Add EC2 URL to README

---
 README.md | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 648c642..63b78f0 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,13 @@ http://tryclj.com
 
 To run it locally, use `lein ring server`.
 
-## Amazon EC2 setup
+## Online
+
+We are running an instance of the 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:
 

From 746aff35ff69e2ffb2bd2c3a97ff438487440a9a Mon Sep 17 00:00:00 2001
From: Tom Crinson 
Date: Thu, 10 Jul 2014 19:33:41 +0100
Subject: [PATCH 16/37] Moved buttons to less prominent position

---
 src/tryclojure/views/home.clj | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj
index ee2b239..62c582d 100644
--- a/src/tryclojure/views/home.clj
+++ b/src/tryclojure/views/home.clj
@@ -59,9 +59,9 @@
         [:span.logo-clojure "Clo" [:em "j"] "ure"]]]
       [:div#container
        [:div#console.console]
+       [:div#changer (home-html)]
        [:div#buttons
         [: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. Sudoku tutorial ©2014 Ray Miller."]]]]]))

From 3fa20bb0a2dd837f50a297554af5c38e616c66e1 Mon Sep 17 00:00:00 2001
From: Tom Crinson 
Date: Thu, 10 Jul 2014 20:35:42 +0100
Subject: [PATCH 17/37] Added in tutorial button

---
 resources/public/javascript/tryclojure.js | 14 +++++++++++---
 resources/public/tutorial.html            |  5 +++++
 src/tryclojure/views/home.clj             |  1 +
 3 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/resources/public/javascript/tryclojure.js b/resources/public/javascript/tryclojure.js
index 4601972..1f67643 100644
--- a/resources/public/javascript/tryclojure.js
+++ b/resources/public/javascript/tryclojure.js
@@ -1,6 +1,6 @@
 var pages;
 
-var currentPage = -1;
+var currentPage = 0;
 
 function pageExitCondition(pageNumber) {
     if (pageNumber >= 0 && pageNumber < pages.length && pages[pageNumber].exitexpr) {
@@ -37,8 +37,8 @@ function goToTag(tag) {
     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) {
@@ -149,6 +149,13 @@ 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; } );
 
@@ -164,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
index f2e0e08..9142f97 100644
--- a/resources/public/tutorial.html
+++ b/resources/public/tutorial.html
@@ -1,4 +1,9 @@
 
+
+

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 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 Date: Thu, 10 Jul 2014 20:42:52 +0100 Subject: [PATCH 18/37] Added slightly darker background to non evaluatable code --- resources/public/css/tryclojure.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index d7d54f9..21f8ed6 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -135,13 +135,17 @@ table.bottom { } #changer code { - background-color: #eee; + background-color: #979797; border: 1px solid #aaa; color: #555; font-family: courier, monospace; padding: 0.1em 0.25em 0.1em 0.25em; } +#changer code.expr{ + background-color: #eee; +} + div#tuttext { } From 7cce47fd9fe502f9ba610d348a3fc56c00a1ab95 Mon Sep 17 00:00:00 2001 From: Joe Townsend Date: Thu, 10 Jul 2014 19:55:39 +0000 Subject: [PATCH 19/37] added explanation of 'for' and fixed partition typo --- resources/public/tutorial.html | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index f2e0e08..6ef0fdb 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -651,11 +651,27 @@ 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 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 etc. + The function in this case is to return the sum of the values. +

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

@@ -665,7 +681,7 @@

Now, we need to partition these values into groups of 3 and - interpose pipe symbols. Luckily, Clojure comes with 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 
Date: Thu, 10 Jul 2014 22:47:19 +0100
Subject: [PATCH 20/37] Fix markdown.

---
 README.md | 58 +++++++++++++++++++++++++++----------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/README.md b/README.md
index 63b78f0..b96833c 100644
--- a/README.md
+++ b/README.md
@@ -20,35 +20,35 @@ http://ec2-54-77-13-3.eu-west-1.compute.amazonaws.com:8801/
 
 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' < /etc/init/tryclj.conf' <
Date: Thu, 10 Jul 2014 22:50:37 +0100
Subject: [PATCH 21/37] Distinguish original from extended online versions.

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index b96833c..8abafea 100644
--- a/README.md
+++ b/README.md
@@ -6,13 +6,13 @@ 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
 
-We are running an instance of the tutorial on EC2
+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/
 

From a00508655dc8ecf44ac516fb8a5139c07f64e529 Mon Sep 17 00:00:00 2001
From: Ray Miller 
Date: Thu, 10 Jul 2014 23:01:45 +0100
Subject: [PATCH 22/37] Specify cell-index function better.

---
 resources/public/tutorial.html | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html
index 6ef0fdb..dbfa1b0 100644
--- a/resources/public/tutorial.html
+++ b/resources/public/tutorial.html
@@ -347,8 +347,11 @@
   

- We now have all the tools we need to write our 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 
Date: Thu, 10 Jul 2014 23:13:06 +0100
Subject: [PATCH 23/37] More explanation of recur.

---
 resources/public/tutorial.html | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html
index dbfa1b0..7d4b28a 100644
--- a/resources/public/tutorial.html
+++ b/resources/public/tutorial.html
@@ -1117,6 +1117,7 @@
             (assoc grid ix new-candidates)))
       
     
+

Putting this all together, we have:

@@ -1348,6 +1349,16 @@ 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...

From 5e5f5d2ed2a5144838b41d884ea6928fa5212003 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Fri, 11 Jul 2014 08:32:06 +0100 Subject: [PATCH 24/37] More explanation of for. --- resources/public/tutorial.html | 64 +++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 7d4b28a..5a3e530 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -644,7 +644,7 @@ 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 @@ -663,18 +663,64 @@

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

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

From 58b542f7bf83e6547d9fd503deaed94b9eec50b8 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Fri, 11 Jul 2014 17:01:00 +0100 Subject: [PATCH 25/37] Add missing bracket to example. --- resources/public/tutorial.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 5a3e530..0cd0297 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -327,7 +327,7 @@ Using destructuring, we can simply write

-      (let [[row col] (map str "A3") ...)
+      (let [[row col] (map str "A3")] ...)
     

(note the extra set of square brackets inside the let). Putting this From a14e0d2d4786db938990d901ba65cf62543ec8b2 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Fri, 11 Jul 2014 19:20:44 +0100 Subject: [PATCH 26/37] Style tweaks --- resources/public/css/tryclojure.css | 13 +++++++------ resources/public/tutorial.html | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index 21f8ed6..185ebd5 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -99,7 +99,7 @@ table.bottom { padding: .3em 1em; margin-right: .7em; font-family: Helvetica, sans-serif; - font-size: 16px; + font-size: 14px; font-weight: bold; background: #90b4fe; border-radius: 5px; @@ -128,14 +128,14 @@ table.bottom { #changer p { background: transparent; - line-height: 1.3em; + line-height: 1.5em; font-family: Arial, serif; - font-size: 16px; + font-size: 14px; color: #555; } #changer code { - background-color: #979797; + background-color: #eee; border: 1px solid #aaa; color: #555; font-family: courier, monospace; @@ -143,7 +143,7 @@ table.bottom { } #changer code.expr{ - background-color: #eee; + background-color: #D8ACDB; } div#tuttext { @@ -160,8 +160,9 @@ div.footer { } pre.codeblock { - background: #f0f0f0; + background: #D8ACDB; border: 1px solid #aaa; + font-size: 14px; padding: 10px 0; } diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 4eb0db5..4af4502 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -86,7 +86,9 @@ "functional" programming languages. Like most functional languages, Clojure can define a function without even giving it a name:

- (fn [x] (* x x)) +
+      (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. @@ -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 for to construct the cell names; for From 30fec79f2ba4046cb5b6f809f7c898c4e7d4e1cd Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sat, 12 Jul 2014 08:35:06 +0100 Subject: [PATCH 27/37] Address Jim's comments. --- resources/public/css/tryclojure.css | 6 +- resources/public/tutorial.html | 344 +++++++++++++++++++--------- src/tryclojure/views/home.clj | 2 +- 3 files changed, 247 insertions(+), 105 deletions(-) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index 185ebd5..e33db15 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -160,12 +160,16 @@ div.footer { } pre.codeblock { - background: #D8ACDB; + background: #eee; border: 1px solid #aaa; font-size: 14px; padding: 10px 0; } +pre.codeblock.expr { + background: #D8ACDB; +} + #changer pre.codeblock code{ background-color: transparent; border: none; diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 4af4502..20c9cce 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -6,13 +6,26 @@

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

+

+ Let's get started: type next.

@@ -33,7 +46,7 @@

A Clojure program is made of lists. - (+ 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.

@@ -86,7 +99,7 @@ "functional" programming languages. Like most functional languages, Clojure can define a function without even giving it a name:

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

@@ -102,7 +115,7 @@ 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 @@ -122,9 +135,9 @@

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

+      
         
-          (def square (fn [x] (* x x)))
+          (def another-square (fn [x] (* x x)))
         
       
to continue. @@ -132,8 +145,9 @@

- Success! Now you can call this new 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)

By now, you know that lists are quite important in Clojure. @@ -172,8 +186,77 @@ (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 - give 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")
+      
+    
+
-

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

With two arguments, - (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).

@@ -247,7 +330,12 @@

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

@@ -292,26 +380,48 @@ (.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"). + (seq "A3").

-
+

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

+
+
+

+ 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

@@ -337,10 +447,18 @@ (let [[row col] (map str "A3")] ...)

- (note the extra set of square brackets inside the let). Putting this - all together, we have: + (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)
@@ -370,6 +488,11 @@
             ...))
       
     
+

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

@@ -379,7 +502,7 @@ Did your function return the value 25? If not, evaluate the following code to define 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 @@
     

With that in hand, try:

-
+    
       
         (map parse-int (re-seq #"\d" puzzle))
       
@@ -508,7 +631,7 @@
     

We can now define the candidates-for function:

-
+    
       
         (defn candidates-for [d]
           (if (zero? d) candidates (hash-set d)))
@@ -517,12 +640,12 @@
     

Try it out for a few values of 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 @@
     

We're almost there! Let's look at the data type of 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 @@
     

Now we can define our grid:

-
+    
       
         (def grid (parse-puzzle puzzle))
       
@@ -593,7 +716,7 @@
     

We can look up an element in a vector using 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 @@
     

Let's use 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.
     

-
+

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

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

and

-
+    
       
         (interpose "|" (partition 3 (range 9)))
       
@@ -755,7 +893,7 @@
     

Of course, we need a pipe at the beginning and the end of each row too:

-
+    
       
         ["|" (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"])
     

and compare that output with

-
+    
       (str "A" "ba" "cus")
     

@@ -795,7 +933,7 @@ by 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 @@
     

With this in hand, we can render a grid row!

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

Let's put all this together to build our render-grid function.

-
+    
       
         (defn render-grid [grid]
           (let [hsep "+---+---+---+"
@@ -876,7 +1014,7 @@
       
     

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

@@ -900,7 +1038,7 @@

Let's define 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 @@
     

You might see some duplicates in this list, but these are easily dealt with:

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

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

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

Putting this all together, we have:

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

Let's try out the assign function:

-
+    
       
         (assign grid "A1" 5)
       
@@ -1318,7 +1456,7 @@
     

And an illegal assignment:

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

This should return 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 @@
     

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

-
+    
       
         (defn find-singleton [grid]
           (first
@@ -1389,7 +1527,7 @@
     

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

-
+    
       
         (defn solve [grid]
           (cond
@@ -1415,7 +1553,7 @@
     

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

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

We can now add search to our solve function:

-
+    
       
         (defn solve [grid]
           (cond
@@ -1531,7 +1669,7 @@
     

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

-
+    
       
         (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 
Date: Thu, 10 Jul 2014 20:35:42 +0100
Subject: [PATCH 28/37] Added in tutorial button

---
 resources/public/javascript/tryclojure.js | 14 +++++++++++---
 resources/public/tutorial.html            |  5 +++++
 src/tryclojure/views/home.clj             |  1 +
 3 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/resources/public/javascript/tryclojure.js b/resources/public/javascript/tryclojure.js
index 4601972..1f67643 100644
--- a/resources/public/javascript/tryclojure.js
+++ b/resources/public/javascript/tryclojure.js
@@ -1,6 +1,6 @@
 var pages;
 
-var currentPage = -1;
+var currentPage = 0;
 
 function pageExitCondition(pageNumber) {
     if (pageNumber >= 0 && pageNumber < pages.length && pages[pageNumber].exitexpr) {
@@ -37,8 +37,8 @@ function goToTag(tag) {
     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) {
@@ -149,6 +149,13 @@ 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; } );
 
@@ -164,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
index 7d4b28a..e4b5921 100644
--- a/resources/public/tutorial.html
+++ b/resources/public/tutorial.html
@@ -1,4 +1,9 @@
 
+
+

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 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 Date: Fri, 11 Jul 2014 08:32:06 +0100 Subject: [PATCH 29/37] More explanation of for. --- resources/public/tutorial.html | 64 +++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index e4b5921..6b352aa 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -649,7 +649,7 @@ 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 @@ -668,18 +668,64 @@

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

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

From 6b71f19a7613f541715e044df0bb36e65f399508 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Fri, 11 Jul 2014 17:01:00 +0100 Subject: [PATCH 30/37] Add missing bracket to example. --- resources/public/tutorial.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 6b352aa..4eb0db5 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -332,7 +332,7 @@ Using destructuring, we can simply write

-      (let [[row col] (map str "A3") ...)
+      (let [[row col] (map str "A3")] ...)
     

(note the extra set of square brackets inside the let). Putting this From 1b30ed478db543023be495bf109a970844e94bc8 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Fri, 11 Jul 2014 19:20:44 +0100 Subject: [PATCH 31/37] Style tweaks --- resources/public/css/tryclojure.css | 13 +++++++------ resources/public/tutorial.html | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index 21f8ed6..185ebd5 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -99,7 +99,7 @@ table.bottom { padding: .3em 1em; margin-right: .7em; font-family: Helvetica, sans-serif; - font-size: 16px; + font-size: 14px; font-weight: bold; background: #90b4fe; border-radius: 5px; @@ -128,14 +128,14 @@ table.bottom { #changer p { background: transparent; - line-height: 1.3em; + line-height: 1.5em; font-family: Arial, serif; - font-size: 16px; + font-size: 14px; color: #555; } #changer code { - background-color: #979797; + background-color: #eee; border: 1px solid #aaa; color: #555; font-family: courier, monospace; @@ -143,7 +143,7 @@ table.bottom { } #changer code.expr{ - background-color: #eee; + background-color: #D8ACDB; } div#tuttext { @@ -160,8 +160,9 @@ div.footer { } pre.codeblock { - background: #f0f0f0; + background: #D8ACDB; border: 1px solid #aaa; + font-size: 14px; padding: 10px 0; } diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 4eb0db5..4af4502 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -86,7 +86,9 @@ "functional" programming languages. Like most functional languages, Clojure can define a function without even giving it a name:

- (fn [x] (* x x)) +
+      (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. @@ -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 for to construct the cell names; for From 9d6837af54aed03664c541ea79a29cf9ec55cf57 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sat, 12 Jul 2014 08:35:06 +0100 Subject: [PATCH 32/37] Address Jim's comments. --- resources/public/css/tryclojure.css | 6 +- resources/public/tutorial.html | 344 +++++++++++++++++++--------- src/tryclojure/views/home.clj | 2 +- 3 files changed, 247 insertions(+), 105 deletions(-) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index 185ebd5..e33db15 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -160,12 +160,16 @@ div.footer { } pre.codeblock { - background: #D8ACDB; + background: #eee; border: 1px solid #aaa; font-size: 14px; padding: 10px 0; } +pre.codeblock.expr { + background: #D8ACDB; +} + #changer pre.codeblock code{ background-color: transparent; border: none; diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 4af4502..20c9cce 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -6,13 +6,26 @@

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

+

+ Let's get started: type next.

@@ -33,7 +46,7 @@

A Clojure program is made of lists. - (+ 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.

@@ -86,7 +99,7 @@ "functional" programming languages. Like most functional languages, Clojure can define a function without even giving it a name:

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

@@ -102,7 +115,7 @@ 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 @@ -122,9 +135,9 @@

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

+      
         
-          (def square (fn [x] (* x x)))
+          (def another-square (fn [x] (* x x)))
         
       
to continue. @@ -132,8 +145,9 @@

- Success! Now you can call this new 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)

By now, you know that lists are quite important in Clojure. @@ -172,8 +186,77 @@ (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 - give 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")
+      
+    
+
-

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

With two arguments, - (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).

@@ -247,7 +330,12 @@

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

@@ -292,26 +380,48 @@ (.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"). + (seq "A3").

-
+

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

+
+
+

+ 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

@@ -337,10 +447,18 @@ (let [[row col] (map str "A3")] ...)

- (note the extra set of square brackets inside the let). Putting this - all together, we have: + (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)
@@ -370,6 +488,11 @@
             ...))
       
     
+

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

@@ -379,7 +502,7 @@ Did your function return the value 25? If not, evaluate the following code to define 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 @@
     

With that in hand, try:

-
+    
       
         (map parse-int (re-seq #"\d" puzzle))
       
@@ -508,7 +631,7 @@
     

We can now define the candidates-for function:

-
+    
       
         (defn candidates-for [d]
           (if (zero? d) candidates (hash-set d)))
@@ -517,12 +640,12 @@
     

Try it out for a few values of 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 @@
     

We're almost there! Let's look at the data type of 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 @@
     

Now we can define our grid:

-
+    
       
         (def grid (parse-puzzle puzzle))
       
@@ -593,7 +716,7 @@
     

We can look up an element in a vector using 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 @@
     

Let's use 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.
     

-
+

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

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

and

-
+    
       
         (interpose "|" (partition 3 (range 9)))
       
@@ -755,7 +893,7 @@
     

Of course, we need a pipe at the beginning and the end of each row too:

-
+    
       
         ["|" (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"])
     

and compare that output with

-
+    
       (str "A" "ba" "cus")
     

@@ -795,7 +933,7 @@ by 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 @@
     

With this in hand, we can render a grid row!

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

Let's put all this together to build our render-grid function.

-
+    
       
         (defn render-grid [grid]
           (let [hsep "+---+---+---+"
@@ -876,7 +1014,7 @@
       
     

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

@@ -900,7 +1038,7 @@

Let's define 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 @@
     

You might see some duplicates in this list, but these are easily dealt with:

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

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

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

Putting this all together, we have:

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

Let's try out the assign function:

-
+    
       
         (assign grid "A1" 5)
       
@@ -1318,7 +1456,7 @@
     

And an illegal assignment:

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

This should return 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 @@
     

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

-
+    
       
         (defn find-singleton [grid]
           (first
@@ -1389,7 +1527,7 @@
     

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

-
+    
       
         (defn solve [grid]
           (cond
@@ -1415,7 +1553,7 @@
     

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

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

We can now add search to our solve function:

-
+    
       
         (defn solve [grid]
           (cond
@@ -1531,7 +1669,7 @@
     

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

-
+    
       
         (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 
Date: Sat, 12 Jul 2014 13:05:16 +0100
Subject: [PATCH 33/37] Intro expanded, signposting

---
 resources/public/tutorial.html | 47 +++++++++++++++++++++++++++++-----
 src/tryclojure/views/home.clj  | 11 ++++++++
 2 files changed, 52 insertions(+), 6 deletions(-)

diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html
index 20c9cce..bf071fa 100644
--- a/resources/public/tutorial.html
+++ b/resources/public/tutorial.html
@@ -288,7 +288,7 @@
       Type 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 @@ -296,6 +296,18 @@ 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 @@ -732,6 +744,11 @@

+
    +
  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: @@ -1016,16 +1033,34 @@

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 - (more on this later). When we assign the value 6 to the + 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. + corresponding candidate lists.

We call the cells we have to eliminate when assigning a value to diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj index e1adea1..6096957 100644 --- a/src/tryclojure/views/home.clj +++ b/src/tryclojure/views/home.clj @@ -35,6 +35,17 @@ (html [:p.bottom "Welcome to Clojure! " + "We are " + [:ul + [:li "Ray Miller @ray1729"] + [:li "Jim Downing @jimdowning"] + [:li "Gareth Rogers"] ] + "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."] [:p.bottom "Type next in the REPL to begin." ])) From 070e7949b7e015ff0b65b62405d21faab724011b Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sat, 12 Jul 2014 13:14:41 +0100 Subject: [PATCH 34/37] More content --- resources/public/tutorial.html | 66 ++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 20c9cce..00601cf 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -288,6 +288,72 @@ 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 From 46cf8ac23d5767e29b964b75c26bbf3104916871 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sat, 12 Jul 2014 13:27:53 +0100 Subject: [PATCH 35/37] Fix layout, linkify links. --- src/tryclojure/views/home.clj | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj index 6096957..78733ff 100644 --- a/src/tryclojure/views/home.clj +++ b/src/tryclojure/views/home.clj @@ -33,21 +33,21 @@ (defn home-html [] (html - [:p.bottom - "Welcome to Clojure! " - "We are " - [:ul - [:li "Ray Miller @ray1729"] - [:li "Jim Downing @jimdowning"] - [:li "Gareth Rogers"] ] - "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."] - [:p.bottom "Type next in the REPL to begin." ])) + [: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 m +onthly 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 From 2cdf9d38c49f73201e19212589825f54f7f81790 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sat, 12 Jul 2014 13:32:54 +0100 Subject: [PATCH 36/37] CSS for changer ul --- resources/public/css/tryclojure.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index e33db15..7fe0c54 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -134,6 +134,14 @@ table.bottom { color: #555; } +#changer ul { + background: transparent; + line-height: 1.5em; + font-family: Arial, serif; + font-size: 14px; + color: #555; +} + #changer code { background-color: #eee; border: 1px solid #aaa; From 9831c941fbd467cb4b60add813e816c9a5b748ad Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sat, 12 Jul 2014 22:28:53 +0100 Subject: [PATCH 37/37] Fix typos and formatting. --- resources/public/css/tryclojure.css | 2 +- resources/public/tutorial.html | 17 ++++++++--------- src/tryclojure/views/home.clj | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/resources/public/css/tryclojure.css b/resources/public/css/tryclojure.css index 7fe0c54..f0743f3 100644 --- a/resources/public/css/tryclojure.css +++ b/resources/public/css/tryclojure.css @@ -134,7 +134,7 @@ table.bottom { color: #555; } -#changer ul { +#changer li { background: transparent; line-height: 1.5em; font-family: Arial, serif; diff --git a/resources/public/tutorial.html b/resources/public/tutorial.html index 6361c04..3ce227a 100644 --- a/resources/public/tutorial.html +++ b/resources/public/tutorial.html @@ -201,7 +201,7 @@ our map: (get m :tea).

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

@@ -1362,7 +1362,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]
           ...)
@@ -1372,7 +1372,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)
@@ -1389,7 +1389,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))
@@ -1401,7 +1401,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)
@@ -1409,7 +1409,6 @@
             (assoc grid ix new-candidates)))
       
     
-

Putting this all together, we have:

@@ -1536,7 +1535,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)]
@@ -1699,7 +1698,7 @@
       the original one! Let's take a look at our solve
       function so far:
     

-
+    
       
         1 (defn solve [grid]
         2   (cond
@@ -1787,6 +1786,6 @@
     

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. + click on the 'links' button below for suggested further reading.

diff --git a/src/tryclojure/views/home.clj b/src/tryclojure/views/home.clj index 78733ff..3ded457 100644 --- a/src/tryclojure/views/home.clj +++ b/src/tryclojure/views/home.clj @@ -41,8 +41,8 @@ [: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 m -onthly Cambridge Clojure meetup: bit.ly/camclj"] + "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