├── .gitignore
├── README.md
├── UNLICENSE
├── deps.edn
├── dev.cljs.edn
├── docs.clj
├── docs
├── 01.read.html
├── 01.read.md
├── 02.eval.html
├── 02.eval.md
├── 03.vec.html
├── 03.vec.md
├── 04.fn.html
├── 04.fn.md
├── 05.cond.html
├── 05.cond.md
├── 06.hash-map.html
├── 06.hash-map.md
├── 07.let.html
├── 07.let.md
├── 08.loop.html
├── 08.loop.md
├── 09.loop.html
├── 09.loop.md
├── 10.regex.html
├── 10.regex.md
├── 11.require.html
├── 11.require.md
├── exercises.md
├── paren-soup-light.css
├── paren-soup-with-compiler.js
├── people.csv
└── style.css
├── exercises
├── cards.clj
├── maze.clj
├── people.clj
├── read.clj
├── shape.clj
├── tic-tac-toe.clj
└── todo.clj
└── src
└── example
└── core_server.clj
/.gitignore:
--------------------------------------------------------------------------------
1 | data_readers.clj
2 | hs_err_pid*.log
3 | pom.xml
4 | pom.xml.asc
5 | **/gen
6 | **/target
7 | .*
8 | !.gitignore
9 | trace.edn
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### A Clojure tutorial with interactive code snippets
2 |
3 | 1. [The Reader](https://oakes.github.io/learn-clojure/01.read.html)
4 | 2. [The Evaluator](https://oakes.github.io/learn-clojure/02.eval.html)
5 | 3. [Vectors and Sets](https://oakes.github.io/learn-clojure/03.vec.html)
6 | 4. [Functions and Lists](https://oakes.github.io/learn-clojure/04.fn.html)
7 | 5. [Conditions](https://oakes.github.io/learn-clojure/05.cond.html)
8 | 6. [Hash Maps](https://oakes.github.io/learn-clojure/06.hash-map.html)
9 | 7. [Local Bindings](https://oakes.github.io/learn-clojure/07.let.html)
10 | 8. [Loops](https://oakes.github.io/learn-clojure/08.loop.html)
11 | 9. [Loops Continued](https://oakes.github.io/learn-clojure/09.loop.html)
12 | 10. [Regular Expressions](https://oakes.github.io/learn-clojure/10.regex.html)
13 | 11. [Namespaces](https://oakes.github.io/learn-clojure/11.require.html)
14 |
15 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
The code you write begins its life merely as text – a stream of characters with no meaning at all to the computer they lie within (and possibly to the author who wrote them, for that matter). In Clojure, it is the job of the reader to parse that text into meaningful parts.
Imagine writing our own reader for Clojure. That may seem like a pointless thing to do since Clojure already has one (and ours will likely be buggier and less complete), but that hasn't stopped programmers from reinventing the wheel in the past. Our wheel may barely move, but it'll be our barely moveable wheel.
When our reader comes across 42
, it should parse it as a single number. When it comes across 1 2 10
, it should parse three separate numbers. To do this, our reader makes a rule:
If I come across a digit 0 through 9, keep reading until I find something that isn't a digit.
That works for the simplest cases, but our reader forgot about negative numbers like -42
, so we add a rule:
If I come across a hyphen followed by a digit, include it in the number.
That's better, but there are still more things to consider, such as 0.5
and 1/2
. After adding new rules to account for decimals and ratios, our reader is getting pretty good at parsing numbers.
To reiterate, our reader receives these numbers as text (in particular, ASCII / UTF-8 numerals) and turns them into a format understandable by the computer, which we will generically call data. The reader turns text into data.
While 0.5
and 1/2
are different textual representations, once they are parsed by our reader they end up more-or-less as the same data. This distinction between the text and the data that it turns into will keep coming up, and is extremely important to keep in mind. Text is for humans, data is for machines (and for humans named Rich Hickey).
In addition to numbers, our reader also needs to parse strings. In Clojure they are surrounded by double quotes:
"Hello, world!"
2 |
The rule for our reader is easy enough:
If I come across a double quote, keep reading until I come across another double quote.
But we have a problem: With this rule, it's impossible to create a string that contains a double quote. If we want to make a string that contains The " character surrounds strings
, we'll get an error, because our reader will stop parsing the string when it gets to the second double quote:
"The " character surrounds strings"
3 |
The typical solution is escaping the internal double quote with a backslash character:
"The \" character surrounds strings"
4 |
So we modify our rule:
If I come across a double quote, keep reading until I come across another double quote not preceded by a backslash.
Here again we see the all-important distinction between the text written by a programmer, and the data that our reader turns it into. The double quotes and escaping are all artifacts of the textual representation – that is, they exist purely to help our reader parse the string from text.
The numbers and strings above are called literals because they represent their value directly. It can be extremely useful to represent values indirectly, which in Clojure is done with symbols. Symbols are names that point to some other value.
even? odd? + -
5 |
Our reader should parse four separate symbols here. So the rule boils down to...
If I come across a non-numeric character, keep reading until I reach whitespace.
Let's stick with just these three data types: numbers, strings, and symbols. Our reader doesn't know what the symbols point to; figuring that out is the job of the evaluator.
Next: The Evaluator -------------------------------------------------------------------------------- /docs/01.read.md: -------------------------------------------------------------------------------- 1 | ## The Reader 2 | 3 | The code you write begins its life merely as text -- a stream of characters with no meaning at all to the computer they lie within (and possibly to the author who wrote them, for that matter). In Clojure, it is the job of the *reader* to parse that text into meaningful parts. 4 | 5 | Imagine writing our own reader for Clojure. That may seem like a pointless thing to do since Clojure already has one (and ours will likely be buggier and less complete), but that hasn't stopped programmers from reinventing the wheel in the past. Our wheel may barely move, but it'll be *our* barely moveable wheel. 6 | 7 | When our reader comes across `42`, it should parse it as a single number. When it comes across `1 2 10`, it should parse three separate numbers. To do this, our reader makes a rule: 8 | 9 | *If I come across a digit 0 through 9, keep reading until I find something that isn't a digit.* 10 | 11 | That works for the simplest cases, but our reader forgot about negative numbers like `-42`, so we add a rule: 12 | 13 | *If I come across a hyphen followed by a digit, include it in the number.* 14 | 15 | That's better, but there are still more things to consider, such as `0.5` and `1/2`. After adding new rules to account for decimals and ratios, our reader is getting pretty good at parsing numbers. 16 | 17 | To reiterate, our reader receives these numbers as *text* (in particular, ASCII / UTF-8 numerals) and turns them into a format understandable by the computer, which we will generically call *data*. **The reader turns text into data.** 18 | 19 | While `0.5` and `1/2` are different textual representations, once they are parsed by our reader they end up more-or-less as the same data. This distinction between the text and the data that it turns into will keep coming up, and is extremely important to keep in mind. Text is for humans, data is for machines (and for humans named Rich Hickey). 20 | 21 | In addition to numbers, our reader also needs to parse strings. In Clojure they are surrounded by double quotes: 22 | 23 | ``` 24 | "Hello, world!" 25 | ``` 26 | 27 | The rule for our reader is easy enough: 28 | 29 | *If I come across a double quote, keep reading until I come across another double quote.* 30 | 31 | But we have a problem: With this rule, it's impossible to create a string that *contains* a double quote. If we want to make a string that contains `The " character surrounds strings`, we'll get an error, because our reader will stop parsing the string when it gets to the second double quote: 32 | 33 | ``` 34 | "The " character surrounds strings" 35 | ``` 36 | 37 | The typical solution is *escaping* the internal double quote with a backslash character: 38 | 39 | ``` 40 | "The \" character surrounds strings" 41 | ``` 42 | 43 | So we modify our rule: 44 | 45 | *If I come across a double quote, keep reading until I come across another double quote not preceded by a backslash.* 46 | 47 | Here again we see the all-important distinction between the *text* written by a programmer, and the *data* that our reader turns it into. The double quotes and escaping are all artifacts of the textual representation -- that is, they exist purely to help our reader parse the string from text. 48 | 49 | The numbers and strings above are called *literals* because they represent their value directly. It can be extremely useful to represent values indirectly, which in Clojure is done with *symbols*. Symbols are names that point to some other value. 50 | 51 | ``` 52 | even? odd? + - 53 | ``` 54 | 55 | Our reader should parse four separate symbols here. So the rule boils down to... 56 | 57 | *If I come across a non-numeric character, keep reading until I reach whitespace.* 58 | 59 | Let's stick with just these three data types: numbers, strings, and symbols. Our reader doesn't know what the symbols point to; figuring that out is the job of the *evaluator*. 60 | -------------------------------------------------------------------------------- /docs/02.eval.html: -------------------------------------------------------------------------------- 1 |Let's take those symbols we looked at before, but this time our reader will hand them off to the evaluator...
even? 2 | odd? 3 | + 4 | - 5 |
They are all pointing to functions! But we are not actually running them; we are merely displaying them. To make the evaluator invoke a function, we must surround it in parens and pass whatever arguments we wish...
(even? 7) 6 | (odd? 7) 7 | (+ 1 2) 8 | (- 3 2) 9 |
When there are nested function calls, the evaluator invokes them, starting with the inner-most ones:
(odd? (+ 1 (- 3 2))) 10 |
The evaluator found these functions by looking them up in a table behind the scenes. The association of a symbol to a globally-accessible value is called a var. You can make your own var using def
:
(def my-name "Alice") 11 |
While the previous vars pointed to functions, this one points to a string. Vars can point to any kind of value. Now wherever we use my-name
, the evaluator will look up the var and return its value:
my-name 12 |
Let's pass it as an argument to the first
function and grab the first letter:
(first my-name) 13 |
The distinction between a symbol and a var is crucial to understand. After our reader parsed my-name
, it was considered a symbol, and after handing it off to the evaluator, it found that it was a var and retrieved its value. Let's visualize the life of my-name
...
text -> symbol -> var
14 | (read) (eval)
15 |
If the evaluator can't find what a symbol is pointing to (for example, it was not def
ed anywhere), it will throw an error. However, there is a way to tell the evaluator to not try to resolve it:
'this-symbol-points-to-nothing 16 |
By putting a single quote before a symbol, we are short-circuiting the evaluator and simply telling Clojure to let it remain as a symbol. This is not a common thing to do, because the entire point of symbols is to point to values, but we'll find some use cases in the future. For now, just keep in mind that a single quote is a simple way to shut off the evaluator.
Previous: The ReaderNext: Vectors and Sets -------------------------------------------------------------------------------- /docs/02.eval.md: -------------------------------------------------------------------------------- 1 | ## The Evaluator 2 | 3 | Let's take those symbols we looked at before, but this time our reader will hand them off to the evaluator... 4 | 5 | ```clojure 6 | even? 7 | odd? 8 | + 9 | - 10 | ``` 11 | 12 | They are all pointing to functions! But we are not actually running them; we are merely displaying them. To make the evaluator *invoke* a function, we must surround it in parens and pass whatever arguments we wish... 13 | 14 | ```clojure 15 | (even? 7) 16 | (odd? 7) 17 | (+ 1 2) 18 | (- 3 2) 19 | ``` 20 | 21 | When there are nested function calls, the evaluator invokes them, starting with the inner-most ones: 22 | 23 | ```clojure 24 | (odd? (+ 1 (- 3 2))) 25 | ``` 26 | 27 | The evaluator found these functions by looking them up in a table behind the scenes. **The association of a symbol to a globally-accessible value is called a var.** You can make your own var using `def`: 28 | 29 | ```clojure 30 | (def my-name "Alice") 31 | ``` 32 | 33 | While the previous vars pointed to functions, this one points to a string. Vars can point to any kind of value. Now wherever we use `my-name`, the evaluator will look up the var and return its value: 34 | 35 | ```clojure 36 | my-name 37 | ``` 38 | 39 | Let's pass it as an argument to the `first` function and grab the first letter: 40 | 41 | ```clojure 42 | (first my-name) 43 | ``` 44 | 45 | The distinction between a symbol and a var is crucial to understand. After our reader parsed `my-name`, it was considered a symbol, and after handing it off to the evaluator, it found that it was a var and retrieved its value. Let's visualize the life of `my-name`... 46 | 47 | ``` 48 | text -> symbol -> var 49 | (read) (eval) 50 | ``` 51 | 52 | If the evaluator can't find what a symbol is pointing to (for example, it was not `def`ed anywhere), it will throw an error. However, there is a way to tell the evaluator to not try to resolve it: 53 | 54 | ```clojure 55 | 'this-symbol-points-to-nothing 56 | ``` 57 | 58 | By putting a single quote before a symbol, we are short-circuiting the evaluator and simply telling Clojure to let it remain as a symbol. This is not a common thing to do, because the entire point of symbols is to point to values, but we'll find some use cases in the future. For now, just keep in mind that a single quote is a simple way to shut off the evaluator. 59 | -------------------------------------------------------------------------------- /docs/03.vec.html: -------------------------------------------------------------------------------- 1 |Now that we understand the difference between the reader and the evaluator, let's make our own reader. We'll start by coming up with a line that contains different things we want to parse:
42 0.5 1/2 foo-bar
2 |
To begin, we'll store this text in a string and put it in a var:
(def text "42 0.5 1/2 foo-bar") 3 |
Our reader will look at each character in that string and try to figure out what to do with it. To do that, we need to break it up into individual characters using the vec
function:
(vec text) 4 |
You can see that the results are surrounded by square brackets, meaning it's a vector. This is a common way of storing a collection of data in Clojure. We should store that in a var as well:
(def chars (vec text)) 5 |
We can pull out individual characters from the vector with get
:
(get chars 0) 6 | (get chars 1) 7 | (get chars 2) 8 |
To make this a bit easier, we'll focus on the first character. Let's store it in a var:
(def first-char (get chars 0)) 9 |
We know this character happens to be a digit, but how would we determine that with code? There are only 10 digits, so let's just make a vector that contains all of them. Notice that we are using a special backslash syntax to write each character, instead of surrounding them in double quotes. This is normally how they are written:
(def digits [\1 \2 \3 \4 \5 \6 \7 \8 \9 \0]) 10 |
Now we want to figure out if first-char
is inside of digits
. One way is to use the .indexOf
function, which will return the index of the value if it finds it:
(.indexOf digits first-char) 11 |
It returns 4, because the \5
character is in the 4th position (starting with 0!). If we search for a character that isn't in there, it returns -1:
(.indexOf digits \e) 12 |
If the name .indexOf
looks odd, it's because it is not actually part of Clojure; it is actually a Java method. Any time you see a function starting with a period, it is reaching down to the underlying platform. Normally Clojure tries not to duplicate functionality that already exists underneath.
While the technique above works, it's not very efficient. The .indexOf
function works by going through every item in the vector until it finds what it's looking for. There is a much better way to check if a value is contained within a finite set of things -- and it's called a set. Let's redefine digits
as a set:
(def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 13 |
We can see that sets are surrounded by #{...}
instead of [...]
. One interesting thing about them is, they don't maintain order. Check it out:
digits 14 |
This is the tradeoff you make between vectors and sets. Vectors maintain order, but their .indexOf
function is slow. Sets can shuffle their contents into any arbitrary order they want, but they are very fast at checking if they contain something. Instead of using .indexOf
, they use a Clojure function:
(contains? digits first-char) 15 | (contains? digits \e) 16 |
As you can see, contains?
just returns true or false. There would be no point to returning an index value like .indexOf
does, since sets don't maintain order in the first place. For our purposes, that's ok, since we just want to check if the character is anywhere inside of digits
.
By now we've used several different functions, but we haven't yet made one. As a simple exercise, we will make a function called square
that takes a number as an argument and returns that number multiplied by itself. So, (square 2)
should give us 4. You can make a function using fn
:
(fn [n] (* n n)) 2 |
When we use fn
, we must give it a vector of symbols representing the arguments. It could have zero or more, but in this case there is only one, which we call n
. Then we get to the body of the function, which multiplies n
by itself.
The only problem is, fn
creates a function object, but there is no var we can refer to it with, so it's a bit hard to use. We can solve this using the same def
mechanism we've used in the past:
(def square (fn [n] (* n n))) 3 |
Now we can call it:
(square 2) 4 | (square 3) 5 | (square 10) 6 |
It turns out that we need to define functions all the time, so there is a shorthand built into the language:
(defn square [n] (* n n)) 7 |
So, defn
does the equivalent of the def
+ fn
combo above, but with a bit less typing. It's nice to see how it works underneath – it's just making a function object and saving it to a var.
Let's return to our set of digits:
(def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 8 |
We can now write a function that tells us if a given character is a digit. In Clojure it is common for functions that return a boolean (true
or false
) to end in a ?
, so we'll call it digit?
, and the argument will be called ch
(short for "character"):
(defn digit? [ch] 9 | (contains? digits ch)) 10 |
And it can be called as you would expect:
(digit? \7) 11 | (digit? \u) 12 |
Let's bring back our vector of characters:
(def chars (vec "42 0.5 1/2 foo-bar")) 13 |
We know we can pull out individual values with get
, but how could we just run our digit?
function on every character? For that, we use map
:
(map digit? chars) 14 |
There is also a closely related function that will use our digit?
function to decide which characters to keep, and thus discard non-digit characters:
(filter digit? chars) 15 |
Or, if you want to do the opposite:
(remove digit? chars) 16 |
Notice something subtle about what these functions are returning. We've seen that normally data is stored in vectors as [...]
, but these functions are returning their values between parens. That's because they are lists, not vectors.
There are many underlying differences between lists and vectors, but the most interesting difference for now is that lists can be lazy, delaying how their values are computed until the latest possible time.
For now, however, you should stick to using vectors as much as possible. You can always convert a list to a vector with the same vec
function we've used before:
(vec (filter digit? chars)) 17 |
There are also versions of map
and filter
that return vectors automatically:
(mapv digit? chars) 18 | (filterv digit? chars) 19 |
One more thing about lists. We've seen before that we can make a vector of digits by writing [\1 \2 \3]
, and a set of digits by writing #{\1 \2 \3}
, but we can't do the equivalent for lists:
(\1 \2 \3) 20 |
We get an error! That's because the evaluator, as we saw previously, tries to call a function whenever it sees parens, and \1
is not a function. In a sense, parens serve "double duty" in Clojure. They invoke functions, and they represent lists. That means, if we want to create a list, we need to "shut off" the evaluator.
We already learned how to do this. Just as we can put a single quote before a symbol to make it stay a symbol, we can put one before a list to make it stay a list:
'(\1 \2 \3) 21 |
Another option is to create a list with the list
function:
(list \1 \2 \3) 22 |
Nonetheless, for most day-to-day tasks, you should stick with vectors when you want an ordered collection, and sets when you want to check if it contains a value. Lists aren't that useful of a data structure in most cases.
Previous: Vectors and SetsNext: Conditions -------------------------------------------------------------------------------- /docs/04.fn.md: -------------------------------------------------------------------------------- 1 | ## Functions and Lists 2 | 3 | By now we've used several different functions, but we haven't yet made one. As a simple exercise, we will make a function called `square` that takes a number as an argument and returns that number multiplied by itself. So, `(square 2)` should give us 4. You can make a function using `fn`: 4 | 5 | ```clojure 6 | (fn [n] (* n n)) 7 | ``` 8 | 9 | When we use `fn`, we must give it a vector of symbols representing the arguments. It could have zero or more, but in this case there is only one, which we call `n`. Then we get to the body of the function, which multiplies `n` by itself. 10 | 11 | The only problem is, `fn` creates a function object, but there is no var we can refer to it with, so it's a bit hard to use. We can solve this using the same `def` mechanism we've used in the past: 12 | 13 | ```clojure 14 | (def square (fn [n] (* n n))) 15 | ``` 16 | 17 | Now we can call it: 18 | 19 | ```clojure 20 | (square 2) 21 | (square 3) 22 | (square 10) 23 | ``` 24 | 25 | It turns out that we need to define functions all the time, so there is a shorthand built into the language: 26 | 27 | ```clojure 28 | (defn square [n] (* n n)) 29 | ``` 30 | 31 | So, `defn` does the equivalent of the `def` + `fn` combo above, but with a bit less typing. It's nice to see how it works underneath -- it's just making a function object and saving it to a var. 32 | 33 | Let's return to our set of digits: 34 | 35 | ```clojure 36 | (def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 37 | ``` 38 | 39 | We can now write a function that tells us if a given character is a digit. In Clojure it is common for functions that return a boolean (`true` or `false`) to end in a `?`, so we'll call it `digit?`, and the argument will be called `ch` (short for "character"): 40 | 41 | ```clojure 42 | (defn digit? [ch] 43 | (contains? digits ch)) 44 | ``` 45 | 46 | And it can be called as you would expect: 47 | 48 | ```clojure 49 | (digit? \7) 50 | (digit? \u) 51 | ``` 52 | 53 | Let's bring back our vector of characters: 54 | 55 | ```clojure 56 | (def chars (vec "42 0.5 1/2 foo-bar")) 57 | ``` 58 | 59 | We know we can pull out individual values with `get`, but how could we just run our `digit?` function on every character? For that, we use `map`: 60 | 61 | ```clojure 62 | (map digit? chars) 63 | ``` 64 | 65 | There is also a closely related function that will use our `digit?` function to decide which characters to keep, and thus discard non-digit characters: 66 | 67 | ```clojure 68 | (filter digit? chars) 69 | ``` 70 | 71 | Or, if you want to do the opposite: 72 | 73 | ```clojure 74 | (remove digit? chars) 75 | ``` 76 | 77 | Notice something subtle about what these functions are returning. We've seen that normally data is stored in vectors as `[...]`, but these functions are returning their values between parens. That's because they are lists, not vectors. 78 | 79 | There are many underlying differences between lists and vectors, but the most interesting difference for now is that lists can be *lazy*, delaying how their values are computed until the latest possible time. 80 | 81 | For now, however, you should stick to using vectors as much as possible. You can always convert a list to a vector with the same `vec` function we've used before: 82 | 83 | ```clojure 84 | (vec (filter digit? chars)) 85 | ``` 86 | 87 | There are also versions of `map` and `filter` that return vectors automatically: 88 | 89 | ```clojure 90 | (mapv digit? chars) 91 | (filterv digit? chars) 92 | ``` 93 | 94 | One more thing about lists. We've seen before that we can make a vector of digits by writing `[\1 \2 \3]`, and a set of digits by writing `#{\1 \2 \3}`, but we can't do the equivalent for lists: 95 | 96 | ```clojure 97 | (\1 \2 \3) 98 | ``` 99 | 100 | We get an error! That's because the evaluator, as we saw previously, tries to call a function whenever it sees parens, and `\1` is not a function. In a sense, parens serve "double duty" in Clojure. They invoke functions, and they represent lists. That means, if we want to create a list, we need to "shut off" the evaluator. 101 | 102 | We already learned how to do this. Just as we can put a single quote before a symbol to make it stay a symbol, we can put one before a list to make it stay a list: 103 | 104 | ```clojure 105 | '(\1 \2 \3) 106 | ``` 107 | 108 | Another option is to create a list with the `list` function: 109 | 110 | ```clojure 111 | (list \1 \2 \3) 112 | ``` 113 | 114 | Nonetheless, for most day-to-day tasks, you should stick with vectors when you want an ordered collection, and sets when you want to check if it contains a value. Lists aren't that useful of a data structure in most cases. 115 | -------------------------------------------------------------------------------- /docs/05.cond.html: -------------------------------------------------------------------------------- 1 |We begin again with our vector of characters:
(def chars (vec "42 0.5 1/2 foo-bar")) 2 |
To begin parsing this into discrete tokens like "42" and "foo-bar", we need to know what category each character is. We already made a digit?
function in the past, but now we'd like a more general function that receives a character and tells us what kind it is. Let's start with this:
(def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 3 | 4 | (defn char-type [ch] 5 | (if (contains? digits ch) 6 | "digit" 7 | "other")) 8 |
This function returns a string because we have many possible categories that we'll want to return. Now we can run it on every character using map
:
(map char-type chars) 9 |
So far it only detects digits, but we can make it return the space
category for spaces by adding another if
branch. Here we can check for the space character by seeing if ch
is equal to \space
, which is Clojure's way of representing that character:
(defn char-type [ch] 10 | (if (contains? digits ch) 11 | "digit" 12 | (if (= ch \space) 13 | "space" 14 | "other"))) 15 |
Now we can run map
again:
(map char-type chars) 16 |
There are a few things we should clean up before going on. First of all, the nested if
s will get really hard to read as we add more. We can improve that by using cond
, which allows us to specify as many branches as we want:
(defn char-type [ch] 17 | (cond 18 | (contains? digits ch) 19 | "digit" 20 | 21 | (= ch \space) 22 | "space" 23 | 24 | true 25 | "other")) 26 |
So with cond
, we just provide a condition followed by what we want it to return. The last condition is simply true
because we want it to be the "catch all" condition, for when the other conditions don't match.
The second thing we should fix is our use of strings. In Clojure, strings aren't normally used for internal labels like this. Instead, there is a special data type that is well suited for it: keywords. They look like this:
(defn char-type [ch] 27 | (cond 28 | (contains? digits ch) 29 | :digit 30 | 31 | (= ch \space) 32 | :space 33 | 34 | true 35 | :other)) 36 |
Keywords are like symbols, but with a colon in front. They are used anywhere in Clojure when an internal label is needed. They happen to be faster than strings for many kinds of operations, which is a nice added benefit.
Previous: Functions and ListsNext: Hash Maps -------------------------------------------------------------------------------- /docs/05.cond.md: -------------------------------------------------------------------------------- 1 | ## Conditions 2 | 3 | We begin again with our vector of characters: 4 | 5 | ```clojure 6 | (def chars (vec "42 0.5 1/2 foo-bar")) 7 | ``` 8 | 9 | To begin parsing this into discrete tokens like "42" and "foo-bar", we need to know what category each character is. We already made a `digit?` function in the past, but now we'd like a more general function that receives a character and tells us what kind it is. Let's start with this: 10 | 11 | ```clojure 12 | (def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 13 | 14 | (defn char-type [ch] 15 | (if (contains? digits ch) 16 | "digit" 17 | "other")) 18 | ``` 19 | 20 | This function returns a string because we have many possible categories that we'll want to return. Now we can run it on every character using `map`: 21 | 22 | ```clojure 23 | (map char-type chars) 24 | ``` 25 | 26 | So far it only detects digits, but we can make it return the `space` category for spaces by adding another `if` branch. Here we can check for the space character by seeing if `ch` is equal to `\space`, which is Clojure's way of representing that character: 27 | 28 | ```clojure 29 | (defn char-type [ch] 30 | (if (contains? digits ch) 31 | "digit" 32 | (if (= ch \space) 33 | "space" 34 | "other"))) 35 | ``` 36 | 37 | Now we can run `map` again: 38 | 39 | ```clojure 40 | (map char-type chars) 41 | ``` 42 | 43 | There are a few things we should clean up before going on. First of all, the nested `if`s will get really hard to read as we add more. We can improve that by using `cond`, which allows us to specify as many branches as we want: 44 | 45 | ```clojure 46 | (defn char-type [ch] 47 | (cond 48 | (contains? digits ch) 49 | "digit" 50 | 51 | (= ch \space) 52 | "space" 53 | 54 | true 55 | "other")) 56 | ``` 57 | 58 | So with `cond`, we just provide a condition followed by what we want it to return. The last condition is simply `true` because we want it to be the "catch all" condition, for when the other conditions don't match. 59 | 60 | The second thing we should fix is our use of strings. In Clojure, strings aren't normally used for internal labels like this. Instead, there is a special data type that is well suited for it: keywords. They look like this: 61 | 62 | ```clojure 63 | (defn char-type [ch] 64 | (cond 65 | (contains? digits ch) 66 | :digit 67 | 68 | (= ch \space) 69 | :space 70 | 71 | true 72 | :other)) 73 | ``` 74 | 75 | Keywords are like symbols, but with a colon in front. They are used anywhere in Clojure when an internal label is needed. They happen to be faster than strings for many kinds of operations, which is a nice added benefit. 76 | 77 | ```clojure 78 | (map char-type chars) 79 | ``` 80 | -------------------------------------------------------------------------------- /docs/06.hash-map.html: -------------------------------------------------------------------------------- 1 |(map char-type chars) 37 |
Let's return to our char-type
function, and this time we'll add a few more things to it:
(def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 2 | 3 | (defn char-type [ch] 4 | (cond 5 | (contains? digits ch) 6 | :digit 7 | 8 | (= ch \space) 9 | :space 10 | 11 | (= ch \.) 12 | :period 13 | 14 | (= ch \/) 15 | :forward-slash 16 | 17 | (= ch \") 18 | :double-quote 19 | 20 | true 21 | :other)) 22 |
Let's map
over the characters to see how it looks with the new additions:
(def chars (vec "42 0.5 1/2 foo-bar")) 23 | 24 | (map char-type chars) 25 |
Notice that most of these branches are just comparing the argument to a single character, and returning a keyword that names it. In Clojure, we can store these simple key-value pairs more elegantly in a hash map:
(def char-names {\space :space 26 | \. :period 27 | \/ :forward-slash 28 | \" :double-quote}) 29 |
We can now look up a given character using get
:
(get char-names \.) 30 |
And we can also use contains?
to check if a given key is in it:
(contains? char-names \") 31 |
With that, we can rewrite char-type
to read from our hash map if it contains the character:
(defn char-type [ch] 32 | (cond 33 | (contains? digits ch) 34 | :digit 35 | 36 | (contains? char-names ch) 37 | (get char-names ch) 38 | 39 | true 40 | :other)) 41 |
And we can see that it works the same way:
(map char-type chars) 42 |
We've already seen vectors, sets, and lists. Hash maps are the fourth major data structure in Clojure. Unlike the example above, most hash maps use keywords as their keys, like this:
(def person {:name "Bob" 43 | :age 33 44 | :country "United States"}) 45 |
When done this way, we don't even need to call (get person :name)
to get the name. Keywords can actually act as functions directly:
(:name person) 46 |
Nonetheless, in our char-names
hash map, we need to call get
, because our keys are characters.
We have seen many examples of symbols that point to vars, including a few that we created ourselves:
(def chars (vec "42 0.5 1/2 foo-bar")) 2 | (def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 3 |
You might be tempted to think that symbols always end up being vars, which contributes to the ongoing confusion about the difference between symbols and vars that we covered earlier.
Remember, vars are for pointing to global values. We've already come across an example of a symbol that is not a var. Recall the digit?
function we made:
(defn digit? [ch] 4 | (contains? digits ch)) 5 |
The ch
is a symbol we created to represent the argument passed to the function. Is it globally accessible? Can I see what ch
is anywhere outside of the defn
above? No, it is only available inside the body of that function. Therefore function arguments are not vars; they are a separate thing.
There is another way to make symbols point to values temporarily, rather than globally, and that is let
. Much like function arguments, symbols that are defined in a let
only point to values while inside the let
itself:
(let [first-char (get chars 0)] 6 | (digit? first-char)) 7 |
Earlier, we made first-char
a var with (def first-char (get chars 0))
. The advantage to using let
instead is that we avoid "polluting" our program with global values when we only need them temporarily.
Keep in mind that you can define as many local symbols as you want in a let
. For example, this checks whether the first two characters are digits:
(let [first-char (get chars 0) 8 | second-char (get chars 1)] 9 | (and (digit? first-char) 10 | (digit? second-char))) 11 |
The previous example could just as easily be written without a let
:
(and (digit? (get chars 0)) 12 | (digit? (get chars 1))) 13 |
However, by using a let, we can assign meaningful names to temporary values, rather than making heavily-nested code where one function call is embedded inside another. So, even if you find you can write code that way, using a let
may make the code more readable.
It's time to bring back the char-type
function we wrote:
(def chars (vec "42 0.5 1/2 foo-bar")) 2 | (def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 3 | (def char-names {\space :space 4 | \. :period 5 | \/ :forward-slash 6 | \" :double-quote}) 7 | 8 | (defn char-type [ch] 9 | (cond 10 | (contains? digits ch) 11 | :digit 12 | 13 | (contains? char-names ch) 14 | (get char-names ch) 15 | 16 | true 17 | :other)) 18 |
One really neat thing we can do is use the partition-by
function. Let's look at what it does:
(partition-by char-type chars) 19 |
If you look at the return value, you'll find a list of lists; partition-by
has split up chars
into smaller groups. You'll notice that any adjacent characters that have the same type are grouped together. For example, the first list is (\4 \2)
because they are both digits.
It might be easier to understand with another example. Here, we partition a vector of numbers according to whether or not they are even:
(partition-by even? [1 1 3 2 4 5 7 3]) 20 |
So when we do (partition-by char-type chars)
it looks at each item in chars
and runs the provided function, char-type
, on it. If it returns the same thing that the last item did, they are grouped together; otherwise, it starts a new group.
Let's see if we can replicate what partition-by
is doing ourselves. Since we generally prefer vectors over lists, let's create a vector of vectors like this: [[\4 \2] [\space] [\0] ...]
. To do that, we need to make a function that takes two arguments: the result vector that we are producing, and the individual character that we're currently looking at.
We can do this by creating a loop
. It needs to store two things: the overall result vector that we want to create, and the index of the character that we're currently looking at. We'll put ,,,
in the body of the loop
as a placeholder for now:
(loop [result [] 21 | index 0] 22 | ,,,) 23 |
It looks quite a bit like a let
, except it can run multiple times, updating the result
and index
each time. For example, if we just want to fill result
with each character and then return it, we could write this:
(loop [result [] 24 | index 0] 25 | (let [ch (get chars index) 26 | new-index (+ index 1)] 27 | (if ch 28 | (let [new-result (conj result ch)] 29 | (recur new-result new-index)) 30 | result))) 31 |
First, we get
the current char. If we are past the end of chars
, it will return nil
. If it isn't nil
, we create a let
where we add it to the result
vector using conj
, and increment the index
by one. Finally, we call recur
, which will cause the loop to run again with the new values. When index
is finally at the end, we just return result
.
So how do we mimic the behavior of partition-by
? What we want to do is store a few more things in the bindings of the loop
. First, let's store the type of the last character we saw. We do this by initializing it as nil
(meaning no value), and then we pass the current char's type into recur
.
(loop [result [] 32 | index 0 33 | last-type nil] 34 | (let [ch (get chars index) 35 | new-type (char-type ch) 36 | new-index (+ index 1)] 37 | (if ch 38 | (let [new-result (conj result ch)] 39 | (recur new-result new-index new-type)) 40 | result))) 41 |
Notice that nothing changed in the output; all we did is make the loop
store the last character's type while it ran. Now let's make it only conj
a character if its type is different than the last type:
(loop [result [] 42 | index 0 43 | last-type nil] 44 | (let [ch (get chars index) 45 | new-type (char-type ch) 46 | new-index (+ index 1)] 47 | (if ch 48 | (if (= last-type new-type) 49 | (recur result new-index last-type) 50 | (let [new-result (conj result ch)] 51 | (recur new-result new-index new-type))) 52 | result))) 53 |
To put them into smaller groups like partition-by
, we create a group
vector that is initially nil
. If the type changed, we make a new group with [ch]
; otherwise, we add it with (conj group ch)
. Then, we add group
to result
instead of ch
, so the result should now be a vector of vectors:
(loop [result [] 54 | index 0 55 | last-type nil 56 | group nil] 57 | (let [ch (get chars index) 58 | new-type (char-type ch) 59 | new-index (+ index 1)] 60 | (if ch 61 | (if (= last-type new-type) 62 | (recur result new-index last-type (conj group ch)) 63 | (let [new-result (conj result group)] 64 | (recur new-result new-index new-type [ch]))) 65 | result))) 66 |
There are a few bugs. We aren't adding the last group, so the final \"
character isn't there. We can do that by adding it to the result
using conj
:
(loop [result [] 67 | index 0 68 | last-type nil 69 | group nil] 70 | (let [ch (get chars index) 71 | new-type (char-type ch) 72 | new-index (+ index 1)] 73 | (if ch 74 | (if (= last-type new-type) 75 | (recur result new-index last-type (conj group ch)) 76 | (let [new-result (conj result group)] 77 | (recur new-result new-index new-type [ch]))) 78 | (conj result group)))) 79 |
Lastly, can you guess why that little nil
is there in the beginning? Think about what happens when it first runs. last-type
is nil
, and the first character's type is :digit
, so it thinks the group has changed and dutifully adds it to result
. We can fix that by making sure we don't add nil
groups to the result
:
Previous: Local BindingsNext: Loops Continued -------------------------------------------------------------------------------- /docs/08.loop.md: -------------------------------------------------------------------------------- 1 | ## Loops 2 | 3 | It's time to bring back the `char-type` function we wrote: 4 | 5 | ```clojure 6 | (def chars (vec "42 0.5 1/2 foo-bar")) 7 | (def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 8 | (def char-names {\space :space 9 | \. :period 10 | \/ :forward-slash 11 | \" :double-quote}) 12 | 13 | (defn char-type [ch] 14 | (cond 15 | (contains? digits ch) 16 | :digit 17 | 18 | (contains? char-names ch) 19 | (get char-names ch) 20 | 21 | true 22 | :other)) 23 | ``` 24 | 25 | One really neat thing we can do is use the `partition-by` function. Let's look at what it does: 26 | 27 | ```clojure 28 | (partition-by char-type chars) 29 | ``` 30 | 31 | If you look at the return value, you'll find a list of lists; `partition-by` has split up `chars` into smaller groups. You'll notice that any adjacent characters that have the same type are grouped together. For example, the first list is `(\4 \2)` because they are both digits. 32 | 33 | It might be easier to understand with another example. Here, we partition a vector of numbers according to whether or not they are even: 34 | 35 | ```clojure 36 | (partition-by even? [1 1 3 2 4 5 7 3]) 37 | ``` 38 | 39 | So when we do `(partition-by char-type chars)` it looks at each item in `chars` and runs the provided function, `char-type`, on it. If it returns the same thing that the last item did, they are grouped together; otherwise, it starts a new group. 40 | 41 | Let's see if we can replicate what `partition-by` is doing ourselves. Since we generally prefer vectors over lists, let's create a vector of vectors like this: `[[\4 \2] [\space] [\0] ...]`. To do that, we need to make a function that takes two arguments: the result vector that we are producing, and the individual character that we're currently looking at. 42 | 43 | We can do this by creating a `loop`. It needs to store two things: the overall result vector that we want to create, and the index of the character that we're currently looking at. We'll put `,,,` in the body of the `loop` as a placeholder for now: 44 | 45 | ```clojure 46 | (loop [result [] 47 | index 0] 48 | ,,,) 49 | ``` 50 | 51 | It looks quite a bit like a `let`, except it can run multiple times, updating the `result` and `index` each time. For example, if we just want to fill `result` with each character and then return it, we could write this: 52 | 53 | ```clojure 54 | (loop [result [] 55 | index 0] 56 | (let [ch (get chars index) 57 | new-index (+ index 1)] 58 | (if ch 59 | (let [new-result (conj result ch)] 60 | (recur new-result new-index)) 61 | result))) 62 | ``` 63 | 64 | First, we `get` the current char. If we are past the end of `chars`, it will return `nil`. If it isn't `nil`, we create a `let` where we add it to the `result` vector using `conj`, and increment the `index` by one. Finally, we call `recur`, which will cause the loop to run again with the new values. When `index` is finally at the end, we just return `result`. 65 | 66 | So how do we mimic the behavior of `partition-by`? What we want to do is store a few more things in the bindings of the `loop`. First, let's store the type of the last character we saw. We do this by initializing it as `nil` (meaning no value), and then we pass the current char's type into `recur`. 67 | 68 | ```clojure 69 | (loop [result [] 70 | index 0 71 | last-type nil] 72 | (let [ch (get chars index) 73 | new-type (char-type ch) 74 | new-index (+ index 1)] 75 | (if ch 76 | (let [new-result (conj result ch)] 77 | (recur new-result new-index new-type)) 78 | result))) 79 | ``` 80 | 81 | Notice that nothing changed in the output; all we did is make the `loop` store the last character's type while it ran. Now let's make it only `conj` a character if its type is different than the last type: 82 | 83 | ```clojure 84 | (loop [result [] 85 | index 0 86 | last-type nil] 87 | (let [ch (get chars index) 88 | new-type (char-type ch) 89 | new-index (+ index 1)] 90 | (if ch 91 | (if (= last-type new-type) 92 | (recur result new-index last-type) 93 | (let [new-result (conj result ch)] 94 | (recur new-result new-index new-type))) 95 | result))) 96 | ``` 97 | 98 | To put them into smaller groups like `partition-by`, we create a `group` vector that is initially `nil`. If the type changed, we make a new group with `[ch]`; otherwise, we add it with `(conj group ch)`. Then, we add `group` to `result` instead of `ch`, so the result should now be a vector of vectors: 99 | 100 | ```clojure 101 | (loop [result [] 102 | index 0 103 | last-type nil 104 | group nil] 105 | (let [ch (get chars index) 106 | new-type (char-type ch) 107 | new-index (+ index 1)] 108 | (if ch 109 | (if (= last-type new-type) 110 | (recur result new-index last-type (conj group ch)) 111 | (let [new-result (conj result group)] 112 | (recur new-result new-index new-type [ch]))) 113 | result))) 114 | ``` 115 | 116 | There are a few bugs. We aren't adding the last group, so the final `\"` character isn't there. We can do that by adding it to the `result` using `conj`: 117 | 118 | ```clojure 119 | (loop [result [] 120 | index 0 121 | last-type nil 122 | group nil] 123 | (let [ch (get chars index) 124 | new-type (char-type ch) 125 | new-index (+ index 1)] 126 | (if ch 127 | (if (= last-type new-type) 128 | (recur result new-index last-type (conj group ch)) 129 | (let [new-result (conj result group)] 130 | (recur new-result new-index new-type [ch]))) 131 | (conj result group)))) 132 | ``` 133 | 134 | Lastly, can you guess why that little `nil` is there in the beginning? Think about what happens when it first runs. `last-type` is `nil`, and the first character's type is `:digit`, so it thinks the group has changed and dutifully adds it to `result`. We can fix that by making sure we don't add `nil` groups to the `result`: 135 | 136 | ```clojure 137 | (loop [result [] 138 | index 0 139 | last-type nil 140 | group nil] 141 | (let [ch (get chars index) 142 | new-type (char-type ch) 143 | new-index (+ index 1)] 144 | (if ch 145 | (if (= last-type new-type) 146 | (recur result new-index last-type (conj group ch)) 147 | (let [new-result (if group 148 | (conj result group) 149 | result)] 150 | (recur new-result new-index new-type [ch]))) 151 | (conj result group)))) 152 | ``` 153 | -------------------------------------------------------------------------------- /docs/09.loop.html: -------------------------------------------------------------------------------- 1 |(loop [result [] 80 | index 0 81 | last-type nil 82 | group nil] 83 | (let [ch (get chars index) 84 | new-type (char-type ch) 85 | new-index (+ index 1)] 86 | (if ch 87 | (if (= last-type new-type) 88 | (recur result new-index last-type (conj group ch)) 89 | (let [new-result (if group 90 | (conj result group) 91 | result)] 92 | (recur new-result new-index new-type [ch]))) 93 | (conj result group)))) 94 |
So far, we've made a function that tells us the type of a given character:
(def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 2 | (def char-names {\space :space 3 | \. :period 4 | \/ :forward-slash 5 | \" :double-quote}) 6 | 7 | (defn char-type [ch] 8 | (cond 9 | (contains? digits ch) 10 | :digit 11 | 12 | (contains? char-names ch) 13 | (get char-names ch) 14 | 15 | true 16 | :other)) 17 |
Then, we looped over an array of characters and grouped them according to their type:
(def chars (vec "42 0.5 1/2 foo-bar")) 18 | 19 | (loop [result [] 20 | index 0 21 | last-type nil 22 | group nil] 23 | (let [ch (get chars index) 24 | new-type (char-type ch) 25 | new-index (+ index 1)] 26 | (if ch 27 | (if (= last-type new-type) 28 | (recur result new-index last-type (conj group ch)) 29 | (let [new-result (if group 30 | (conj result group) 31 | result)] 32 | (recur new-result new-index new-type [ch]))) 33 | (conj result group)))) 34 |
We now want to combine these grouped characters into strings. One way to do that is with the str
function:
(str \h \e \l \l \o) 35 |
So now we can change our loop to store the groups as strings rather than in vectors by simply changing how the group is passed to recur
:
(loop [result [] 36 | index 0 37 | last-type nil 38 | group nil] 39 | (let [ch (get chars index) 40 | new-type (char-type ch) 41 | new-index (+ index 1)] 42 | (if ch 43 | (if (= last-type new-type) 44 | (recur result new-index last-type (str group ch)) 45 | (let [new-result (if group 46 | (conj result group) 47 | result)] 48 | (recur new-result new-index new-type (str ch)))) 49 | (conj result group)))) 50 |
While this is a good start, it's not quite what we want. A reader needs to treat 1.5
as a discrete number, so instead of ["0" "." "5"]
we really just want "0.5"
. To do this, let's make a more sophisticated function for deciding if the types are the same:
(defn same-type? [left-type right-type] 51 | (or (= left-type right-type) 52 | (= [left-type right-type] 53 | [:digit :period]))) 54 |
Then use it where we are currently are doing (= last-type new-type)
:
(loop [result [] 55 | index 0 56 | last-type nil 57 | group nil] 58 | (let [ch (get chars index) 59 | new-type (char-type ch) 60 | new-index (+ index 1)] 61 | (if ch 62 | (if (same-type? last-type new-type) 63 | (recur result new-index last-type (str group ch)) 64 | (let [new-result (if group 65 | (conj result group) 66 | result)] 67 | (recur new-result new-index new-type (str ch)))) 68 | (conj result group)))) 69 |
Now our 0.5
is merged together! Let's finally put this loop in a function so we can easily call it:
(defn partition-chars [chars] 70 | (loop [result [] 71 | index 0 72 | last-type nil 73 | group nil] 74 | (let [ch (get chars index) 75 | new-type (char-type ch) 76 | new-index (+ index 1)] 77 | (if ch 78 | (if (same-type? last-type new-type) 79 | (recur result new-index last-type (str group ch)) 80 | (let [new-result (if group 81 | (conj result group) 82 | result)] 83 | (recur new-result new-index new-type (str ch)))) 84 | (conj result group))))) 85 |
Now we can call it like this:
(partition-chars chars) 86 |
Notice that the function argument is called chars
. It is perfectly fine for a var and a function argument to have the same name. Inside the function, the evaluator will resolve chars
as the argument. Outside the function, chars
will resolve to the var as usual. This is called shadowing.
Now we can easily start merging other things. Let's make fractions merge together:
(defn same-type? [left-type right-type] 87 | (or (= left-type right-type) 88 | (= [left-type right-type] 89 | [:digit :period]) 90 | (= [left-type right-type] 91 | [:digit :forward-slash]))) 92 |
And now 1/2
is a single string:
(partition-chars chars) 93 |
There's a simpler way to write this. We can put all "merge pairs" in a set:
(def merge-pairs #{[:digit :period] 94 | [:digit :forward-slash]}) 95 |
And then make same-type?
check if the current pair is in it:
(defn same-type? [left-type right-type] 96 | (or (= left-type right-type) 97 | (contains? merge-pairs [left-type right-type]))) 98 |
It should still work the same way:
Previous: LoopsNext: Regular Expressions -------------------------------------------------------------------------------- /docs/09.loop.md: -------------------------------------------------------------------------------- 1 | ## Loops Continued 2 | 3 | So far, we've made a function that tells us the type of a given character: 4 | 5 | ```clojure 6 | (def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0}) 7 | (def char-names {\space :space 8 | \. :period 9 | \/ :forward-slash 10 | \" :double-quote}) 11 | 12 | (defn char-type [ch] 13 | (cond 14 | (contains? digits ch) 15 | :digit 16 | 17 | (contains? char-names ch) 18 | (get char-names ch) 19 | 20 | true 21 | :other)) 22 | ``` 23 | 24 | Then, we looped over an array of characters and grouped them according to their type: 25 | 26 | ```clojure 27 | (def chars (vec "42 0.5 1/2 foo-bar")) 28 | 29 | (loop [result [] 30 | index 0 31 | last-type nil 32 | group nil] 33 | (let [ch (get chars index) 34 | new-type (char-type ch) 35 | new-index (+ index 1)] 36 | (if ch 37 | (if (= last-type new-type) 38 | (recur result new-index last-type (conj group ch)) 39 | (let [new-result (if group 40 | (conj result group) 41 | result)] 42 | (recur new-result new-index new-type [ch]))) 43 | (conj result group)))) 44 | ``` 45 | 46 | We now want to combine these grouped characters into strings. One way to do that is with the `str` function: 47 | 48 | ```clojure 49 | (str \h \e \l \l \o) 50 | ``` 51 | 52 | So now we can change our loop to store the groups as strings rather than in vectors by simply changing how the group is passed to `recur`: 53 | 54 | ```clojure 55 | (loop [result [] 56 | index 0 57 | last-type nil 58 | group nil] 59 | (let [ch (get chars index) 60 | new-type (char-type ch) 61 | new-index (+ index 1)] 62 | (if ch 63 | (if (= last-type new-type) 64 | (recur result new-index last-type (str group ch)) 65 | (let [new-result (if group 66 | (conj result group) 67 | result)] 68 | (recur new-result new-index new-type (str ch)))) 69 | (conj result group)))) 70 | ``` 71 | 72 | While this is a good start, it's not quite what we want. A reader needs to treat `1.5` as a discrete number, so instead of `["0" "." "5"]` we really just want `"0.5"`. To do this, let's make a more sophisticated function for deciding if the types are the same: 73 | 74 | ```clojure 75 | (defn same-type? [left-type right-type] 76 | (or (= left-type right-type) 77 | (= [left-type right-type] 78 | [:digit :period]))) 79 | ``` 80 | 81 | Then use it where we are currently are doing `(= last-type new-type)`: 82 | 83 | ```clojure 84 | (loop [result [] 85 | index 0 86 | last-type nil 87 | group nil] 88 | (let [ch (get chars index) 89 | new-type (char-type ch) 90 | new-index (+ index 1)] 91 | (if ch 92 | (if (same-type? last-type new-type) 93 | (recur result new-index last-type (str group ch)) 94 | (let [new-result (if group 95 | (conj result group) 96 | result)] 97 | (recur new-result new-index new-type (str ch)))) 98 | (conj result group)))) 99 | ``` 100 | 101 | Now our `0.5` is merged together! Let's finally put this loop in a function so we can easily call it: 102 | 103 | ```clojure 104 | (defn partition-chars [chars] 105 | (loop [result [] 106 | index 0 107 | last-type nil 108 | group nil] 109 | (let [ch (get chars index) 110 | new-type (char-type ch) 111 | new-index (+ index 1)] 112 | (if ch 113 | (if (same-type? last-type new-type) 114 | (recur result new-index last-type (str group ch)) 115 | (let [new-result (if group 116 | (conj result group) 117 | result)] 118 | (recur new-result new-index new-type (str ch)))) 119 | (conj result group))))) 120 | ``` 121 | 122 | Now we can call it like this: 123 | 124 | ```clojure 125 | (partition-chars chars) 126 | ``` 127 | 128 | Notice that the function argument is called `chars`. It is perfectly fine for a var and a function argument to have the same name. Inside the function, the evaluator will resolve `chars` as the argument. Outside the function, `chars` will resolve to the var as usual. This is called shadowing. 129 | 130 | Now we can easily start merging other things. Let's make fractions merge together: 131 | 132 | ```clojure 133 | (defn same-type? [left-type right-type] 134 | (or (= left-type right-type) 135 | (= [left-type right-type] 136 | [:digit :period]) 137 | (= [left-type right-type] 138 | [:digit :forward-slash]))) 139 | ``` 140 | 141 | And now `1/2` is a single string: 142 | 143 | ```clojure 144 | (partition-chars chars) 145 | ``` 146 | 147 | There's a simpler way to write this. We can put all "merge pairs" in a set: 148 | 149 | ```clojure 150 | (def merge-pairs #{[:digit :period] 151 | [:digit :forward-slash]}) 152 | ``` 153 | 154 | And then make `same-type?` check if the current pair is in it: 155 | 156 | ```clojure 157 | (defn same-type? [left-type right-type] 158 | (or (= left-type right-type) 159 | (contains? merge-pairs [left-type right-type]))) 160 | ``` 161 | 162 | It should still work the same way: 163 | 164 | ```clojure 165 | (partition-chars chars) 166 | ``` 167 | -------------------------------------------------------------------------------- /docs/10.regex.html: -------------------------------------------------------------------------------- 1 |(partition-chars chars) 99 |
While the technique we've used so far for building our reader gave a nice tour of the language, things are starting to get complicated. We still can't parse strings, negative numbers, and many other things. It's time to reset and use a better approach.
We'll now turn to regular expressions, which is a separate language for string parsing that is built into the JVM and browser runtimes. Let's start again with our text, and this time we won't break it into separate characters:
(def text "42 0.5 1/2 foo-bar") 2 |
To look for the first digit in this string, we can use re-find
like this:
(re-find #"\d" text) 3 |
Regular expressions look like strings with a #
in the beginning, and \d
is a special flag for finding digits. If we want to find one or more digits, we just add a +
to it:
(re-find #"\d+" text) 4 |
This won't work for decimals though:
(re-find #"\d+" "0.5") 5 |
We represent periods with \.
because a standalone .
as a special meaning. So we can match decimals by saying "one or more digits followed by a period followed by one or more digits":
(re-find #"\d+\.\d+" "0.5") 6 |
We can do something similar for fractions:
(re-find #"\d+/\d+" "1/2") 7 |
Spaces are easy. To find one or more spaces, just do this:
(re-find #" +" text) 8 |
To say that the regular expression must match the very beginning of the string, you just put a ^
in front:
(re-find #"^ +" text) 9 |
It returned nil
this time, because text
does not begin with a space. In Clojure, you can use or
to run several expressions until one of them returns a non-nil value:
(or (re-find #"^ +" text) 10 | (re-find #"^\d+\.\d+" text) 11 | (re-find #"^\d+/\d+" text) 12 | (re-find #"^\d+" text)) 13 |
Now let's put that in a function:
(defn read-next-token [text] 14 | (or (re-find #"^ +" text) 15 | (re-find #"^\d+\.\d+" text) 16 | (re-find #"^\d+/\d+" text) 17 | (re-find #"^\d+" text))) 18 |
The next step is to make a loop that continuously reads tokens from text
until it can't anymore. To do that, we'll use an index
like before, and cut off the beginning of the string as we move through it. We can do this with subs
. For example, to remove the first two characters:
(subs text 2) 19 |
So let's begin our loop with this:
(loop [result [] 20 | index 0] 21 | ,,,) 22 |
First, call subs
to get the substring, and give that to read-next-token
:
(loop [result [] 23 | index 0] 24 | (let [sub-text (subs text index) 25 | token (read-next-token sub-text)] 26 | ,,,)) 27 |
Then, if token
is not nil, add it to result
and increment the index
. Otherwise, return result
:
(loop [result [] 28 | index 0] 29 | (let [sub-text (subs text index) 30 | token (read-next-token sub-text)] 31 | (if token 32 | (recur (conj result token) (+ index (count token))) 33 | result))) 34 |
We already have a pretty good tokenizer! But it didn't find foo-bar
because we have no regex for symbols. If we want to say "one or more characters that are neither digits nor spaces", we can do this:
(re-find #"^[^\d ]+" "foo-bar") 35 |
This isn't perfect, but we'll work with it for now. Let's add it to our function:
(defn read-next-token [text] 36 | (or (re-find #"^ +" text) 37 | (re-find #"^\d+\.\d+" text) 38 | (re-find #"^\d+/\d+" text) 39 | (re-find #"^\d+" text) 40 | (re-find #"^[^\d ]+" text))) 41 |
No we can re-run our loop, and it parses the symbol correctly:
(loop [result [] 42 | index 0] 43 | (let [sub-text (subs text index) 44 | token (read-next-token sub-text)] 45 | (if token 46 | (recur (conj result token) (+ index (count token))) 47 | result))) 48 |
Let's add something we haven't yet: comments! They'll help us remember what each regex matches. In Clojure, they start with a semicolon:
Previous: Loops ContinuedNext: Namespaces -------------------------------------------------------------------------------- /docs/10.regex.md: -------------------------------------------------------------------------------- 1 | ## Regular Expressions 2 | 3 | While the technique we've used so far for building our reader gave a nice tour of the language, things are starting to get complicated. We still can't parse strings, negative numbers, and many other things. It's time to reset and use a better approach. 4 | 5 | We'll now turn to regular expressions, which is a separate language for string parsing that is built into the JVM and browser runtimes. Let's start again with our text, and this time we won't break it into separate characters: 6 | 7 | ```clojure 8 | (def text "42 0.5 1/2 foo-bar") 9 | ``` 10 | 11 | To look for the first digit in this string, we can use `re-find` like this: 12 | 13 | ```clojure 14 | (re-find #"\d" text) 15 | ``` 16 | 17 | Regular expressions look like strings with a `#` in the beginning, and `\d` is a special flag for finding digits. If we want to find one or more digits, we just add a `+` to it: 18 | 19 | ```clojure 20 | (re-find #"\d+" text) 21 | ``` 22 | 23 | This won't work for decimals though: 24 | 25 | ```clojure 26 | (re-find #"\d+" "0.5") 27 | ``` 28 | 29 | We represent periods with `\\.` because a standalone `.` as a special meaning. So we can match decimals by saying "one or more digits followed by a period followed by one or more digits": 30 | 31 | ```clojure 32 | (re-find #"\d+\.\d+" "0.5") 33 | ``` 34 | 35 | We can do something similar for fractions: 36 | 37 | ```clojure 38 | (re-find #"\d+/\d+" "1/2") 39 | ``` 40 | 41 | Spaces are easy. To find one or more spaces, just do this: 42 | 43 | ```clojure 44 | (re-find #" +" text) 45 | ``` 46 | 47 | To say that the regular expression *must* match the very beginning of the string, you just put a `^` in front: 48 | 49 | ```clojure 50 | (re-find #"^ +" text) 51 | ``` 52 | 53 | It returned `nil` this time, because `text` does not begin with a space. In Clojure, you can use `or` to run several expressions until one of them returns a non-nil value: 54 | 55 | ```clojure 56 | (or (re-find #"^ +" text) 57 | (re-find #"^\d+\.\d+" text) 58 | (re-find #"^\d+/\d+" text) 59 | (re-find #"^\d+" text)) 60 | ``` 61 | 62 | Now let's put that in a function: 63 | 64 | ```clojure 65 | (defn read-next-token [text] 66 | (or (re-find #"^ +" text) 67 | (re-find #"^\d+\.\d+" text) 68 | (re-find #"^\d+/\d+" text) 69 | (re-find #"^\d+" text))) 70 | ``` 71 | 72 | The next step is to make a loop that continuously reads tokens from `text` until it can't anymore. To do that, we'll use an `index` like before, and cut off the beginning of the string as we move through it. We can do this with `subs`. For example, to remove the first two characters: 73 | 74 | ```clojure 75 | (subs text 2) 76 | ``` 77 | 78 | So let's begin our loop with this: 79 | 80 | ```clojure 81 | (loop [result [] 82 | index 0] 83 | ,,,) 84 | ``` 85 | 86 | First, call `subs` to get the substring, and give that to `read-next-token`: 87 | 88 | ```clojure 89 | (loop [result [] 90 | index 0] 91 | (let [sub-text (subs text index) 92 | token (read-next-token sub-text)] 93 | ,,,)) 94 | ``` 95 | 96 | Then, if `token` is not nil, add it to `result` and increment the `index`. Otherwise, return `result`: 97 | 98 | ```clojure 99 | (loop [result [] 100 | index 0] 101 | (let [sub-text (subs text index) 102 | token (read-next-token sub-text)] 103 | (if token 104 | (recur (conj result token) (+ index (count token))) 105 | result))) 106 | ``` 107 | 108 | We already have a pretty good tokenizer! But it didn't find `foo-bar` because we have no regex for symbols. If we want to say "one or more characters that are neither digits nor spaces", we can do this: 109 | 110 | ```clojure 111 | (re-find #"^[^\d ]+" "foo-bar") 112 | ``` 113 | 114 | This isn't perfect, but we'll work with it for now. Let's add it to our function: 115 | 116 | ```clojure 117 | (defn read-next-token [text] 118 | (or (re-find #"^ +" text) 119 | (re-find #"^\d+\.\d+" text) 120 | (re-find #"^\d+/\d+" text) 121 | (re-find #"^\d+" text) 122 | (re-find #"^[^\d ]+" text))) 123 | ``` 124 | 125 | No we can re-run our loop, and it parses the symbol correctly: 126 | 127 | ```clojure 128 | (loop [result [] 129 | index 0] 130 | (let [sub-text (subs text index) 131 | token (read-next-token sub-text)] 132 | (if token 133 | (recur (conj result token) (+ index (count token))) 134 | result))) 135 | ``` 136 | 137 | Let's add something we haven't yet: comments! They'll help us remember what each regex matches. In Clojure, they start with a semicolon: 138 | 139 | ```clojure 140 | (defn read-next-token [text] 141 | (or ; spaces 142 | (re-find #"^ +" text) 143 | ; float 144 | (re-find #"^\d+\.\d+" text) 145 | ; ratio 146 | (re-find #"^\d+/\d+" text) 147 | ; integer 148 | (re-find #"^\d+" text) 149 | ; symbol 150 | (re-find #"^[^\d ]+" text))) 151 | ``` 152 | -------------------------------------------------------------------------------- /docs/11.require.html: -------------------------------------------------------------------------------- 1 |(defn read-next-token [text] 49 | (or ; spaces 50 | (re-find #"^ +" text) 51 | ; float 52 | (re-find #"^\d+\.\d+" text) 53 | ; ratio 54 | (re-find #"^\d+/\d+" text) 55 | ; integer 56 | (re-find #"^\d+" text) 57 | ; symbol 58 | (re-find #"^[^\d ]+" text))) 59 |
Let's bring back our new regex-based tokenizer. We'll put our loop in a function called read-tokens
as well:
(defn read-next-token [text] 2 | (or ; spaces 3 | (re-find #"^ +" text) 4 | ; float 5 | (re-find #"^\d+\.\d+" text) 6 | ; ratio 7 | (re-find #"^\d+/\d+" text) 8 | ; integer 9 | (re-find #"^\d+" text) 10 | ; symbol 11 | (re-find #"^[^\d ]+" text))) 12 | 13 | (defn read-tokens [text] 14 | (loop [result [] 15 | index 0] 16 | (let [sub-text (subs text index) 17 | token (read-next-token sub-text)] 18 | (if token 19 | (recur (conj result token) (+ index (count token))) 20 | result)))) 21 |
And now we can call it like this:
(def text "42 0.5 1/2 foo-bar") 22 | 23 | (read-tokens text) 24 |
While this is a great start, a real reader needs to convert those strings into the values they represent. In order words, "42"
should be a number object, and "foo-bar"
should be a symbol object. Right now, both are still just strings.
We can fix this, but we need to finally confront something we haven't talked about yet: namespaces. We haven't yet explicitly dealt with namespaces, but they are a very important part of Clojure.
All vars live in a namespace, whose name is a symbol with one or more periods inside. The primary namespace of Clojure, which is available by default, is clojure.core
, which contains all the functions we have used so far.
To successfully convert a string to a number in Clojure, the easiest approach is to use a function called read-string
. While there is one in clojure.core
, it is not particularly safe to use because it can execute code. There is a more restricted version in the clojure.edn
namespace that we want to use.
This illustrates an important point: Namespaces allow us to create functions with the same name without clashing. If we want to use functions from clojure.edn
, we just need to use the require
function:
(require 'clojure.edn)
25 |
Notice the quoted symbol, which we've seen before. Now we can call the read-string
function from there like this:
(clojure.edn/read-string "42")
26 |
This can be annoying to type, so it's possible to assign a shorter name to the namespace, called an alias, like this:
(require '[clojure.edn :as edn])
27 |
28 | (edn/read-string "42")
29 |
These particular examples can't run in a browser, because ClojureScript doesn't have that namespace. It does, however, have a similar one, called cljs.reader
, that we can use:
(cljs.reader/read-string "42") 30 |
Now we want to run this function on everything that read-tokens
returned. We can do that with map
:
(map cljs.reader/read-string (read-tokens text)) 31 |
Notice that it contains a bunch of nil
s, because read-string
returns nil
when you give it whitespace. We just want to remove those. One way is to use the remove
function we saw previously:
(remove nil? (map cljs.reader/read-string (read-tokens text))) 32 |
However, there's an even easier way to do this using keep
, which works the same as map
but automatically removes nil
s:
Previous: Regular Expressions -------------------------------------------------------------------------------- /docs/11.require.md: -------------------------------------------------------------------------------- 1 | ## Namespaces 2 | 3 | Let's bring back our new regex-based tokenizer. We'll put our loop in a function called `read-tokens` as well: 4 | 5 | ```clojure 6 | (defn read-next-token [text] 7 | (or ; spaces 8 | (re-find #"^ +" text) 9 | ; float 10 | (re-find #"^\d+\.\d+" text) 11 | ; ratio 12 | (re-find #"^\d+/\d+" text) 13 | ; integer 14 | (re-find #"^\d+" text) 15 | ; symbol 16 | (re-find #"^[^\d ]+" text))) 17 | 18 | (defn read-tokens [text] 19 | (loop [result [] 20 | index 0] 21 | (let [sub-text (subs text index) 22 | token (read-next-token sub-text)] 23 | (if token 24 | (recur (conj result token) (+ index (count token))) 25 | result)))) 26 | ``` 27 | 28 | And now we can call it like this: 29 | 30 | ```clojure 31 | (def text "42 0.5 1/2 foo-bar") 32 | 33 | (read-tokens text) 34 | ``` 35 | 36 | While this is a great start, a real reader needs to convert those strings into the values they represent. In order words, `"42"` should be a number object, and `"foo-bar"` should be a symbol object. Right now, both are still just strings. 37 | 38 | We can fix this, but we need to finally confront something we haven't talked about yet: namespaces. We haven't yet explicitly dealt with namespaces, but they are a very important part of Clojure. 39 | 40 | All vars live in a namespace, whose name is a symbol with one or more periods inside. The primary namespace of Clojure, which is available by default, is `clojure.core`, which contains all the functions we have used so far. 41 | 42 | To successfully convert a string to a number in Clojure, the easiest approach is to use a function called `read-string`. While there is one in `clojure.core`, it is not particularly safe to use because it can execute code. There is a more restricted version in the `clojure.edn` namespace that we want to use. 43 | 44 | This illustrates an important point: Namespaces allow us to create functions with the same name without clashing. If we want to use functions from `clojure.edn`, we just need to use the `require` function: 45 | 46 | ``` 47 | (require 'clojure.edn) 48 | ``` 49 | 50 | Notice the quoted symbol, which we've seen before. Now we can call the `read-string` function from there like this: 51 | 52 | ``` 53 | (clojure.edn/read-string "42") 54 | ``` 55 | 56 | This can be annoying to type, so it's possible to assign a shorter name to the namespace, called an alias, like this: 57 | 58 | ``` 59 | (require '[clojure.edn :as edn]) 60 | 61 | (edn/read-string "42") 62 | ``` 63 | 64 | These particular examples can't run in a browser, because ClojureScript doesn't have that namespace. It does, however, have a similar one, called `cljs.reader`, that we can use: 65 | 66 | ```clojure 67 | (cljs.reader/read-string "42") 68 | ``` 69 | 70 | Now we want to run this function on everything that `read-tokens` returned. We can do that with `map`: 71 | 72 | ```clojure 73 | (map cljs.reader/read-string (read-tokens text)) 74 | ``` 75 | 76 | Notice that it contains a bunch of `nil`s, because `read-string` returns `nil` when you give it whitespace. We just want to remove those. One way is to use the `remove` function we saw previously: 77 | 78 | ```clojure 79 | (remove nil? (map cljs.reader/read-string (read-tokens text))) 80 | ``` 81 | 82 | However, there's an even easier way to do this using `keep`, which works the same as `map` but automatically removes `nil`s: 83 | 84 | ```clojure 85 | (keep cljs.reader/read-string (read-tokens text)) 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/exercises.md: -------------------------------------------------------------------------------- 1 | Should I use a vector or set? 2 | 3 | * Pre-flight checklist 4 | * List of blocked phone numbers 5 | * Notes in a chord 6 | * Christmas list 7 | * Spell checker 8 | 9 | A tic-tac-toe checker: 10 | 11 | ```clojure 12 | (def board [:x :e :o 13 | :x :e :e 14 | :x :e :o]) 15 | 16 | (defn solve [board] 17 | (let [[a b c 18 | d e f 19 | g h i] board 20 | solutions (hash-set 21 | [a b c] 22 | [d e f] 23 | [g h i] 24 | [a d g] 25 | [b e h] 26 | [c f i] 27 | [a e i] 28 | [c e g])] 29 | (cond 30 | (contains? solutions [:x :x :x]) :x 31 | (contains? solutions [:o :o :o]) :o 32 | :else nil))) 33 | ``` 34 | 35 | Find all flushes in a deck of cards: 36 | 37 | ```clojure 38 | (def suits [:clubs :spades :hearts :diamonds]) 39 | (def ranks (range 1 14)) 40 | (def rank-names {1 :ace, 11 :jack, 12 :queen, 13 :king}) 41 | 42 | (defonce deck 43 | (set (for [suit suits 44 | rank ranks] 45 | {:suit suit 46 | :rank (get rank-names rank rank)}))) 47 | 48 | (defonce hands 49 | (set (for [c1 deck 50 | c2 (disj deck c1) 51 | c3 (disj deck c1 c2) 52 | c4 (disj deck c1 c2 c3)] 53 | #{c1 c2 c3 c4}))) 54 | 55 | (defn flush? [hand] 56 | (= 1 (count (set (map :suit hand))))) 57 | 58 | (count (filter flush? hands)) 59 | ``` 60 | 61 | Write the following functions: 62 | 63 | * `straight-flush?` returns true if the given cards have the same suit and the ranks are in a sequence 64 | * `straight?` returns true if the given cards' ranks are in a sequence but are not a straight flush 65 | * `four-of-a-kind?` returns true if the given cards have the same rank 66 | * `three-of-a-kind?` returns true if only 3 of the given cards have the same rank 67 | * `two-pair?` returns true if 2 of the given cards have the same rank, and the other 2 have the same rank 68 | 69 | Parse a CSV file: 70 | 71 | ```clojure 72 | (let [people (slurp "people.csv") 73 | people (str/split-lines people) 74 | people (map (fn [line] 75 | (str/split line #",")) 76 | people) 77 | header (first people) 78 | people (rest people) 79 | people (map (fn [line] 80 | (zipmap header line)) 81 | people) 82 | people (filter (fn [line] 83 | (= (get line "country") "Brazil")) 84 | people)] 85 | people) 86 | ``` 87 | 88 | Generate a maze: 89 | 90 | ```clojure 91 | (def size 10) 92 | 93 | (def rooms 94 | (vec (for [row (range 0 size)] 95 | (vec (for [col (range 0 size)] 96 | {:row row, :col col, :visitable? true, 97 | :bottom? true, :right? true}))))) 98 | 99 | (defn possible-neighbors [rooms row col] 100 | [(get-in rooms [(- row 1) col]) 101 | (get-in rooms [(+ row 1) col]) 102 | (get-in rooms [row (- col 1)]) 103 | (get-in rooms [row (+ col 1)])]) 104 | 105 | (defn random-neighbor [rooms row col] 106 | (let [neighbors (possible-neighbors rooms row col) 107 | neighbors (filter :visitable? neighbors)] 108 | (first (shuffle neighbors)))) 109 | 110 | (defn tear-down-wall [rooms old-row old-col new-row new-col] 111 | (cond 112 | ; going up 113 | (< new-row old-row) 114 | (assoc-in rooms [new-row new-col :bottom?] false) 115 | ;going down 116 | (> new-row old-row) 117 | (assoc-in rooms [old-row old-col :bottom?] false) 118 | ; going left 119 | (< new-col old-col) 120 | (assoc-in rooms [new-row new-col :right?] false) 121 | ; going right 122 | (> new-col old-col) 123 | (assoc-in rooms [old-row old-col :right?] false))) 124 | 125 | (defn create-maze [rooms row col] 126 | (let [rooms (assoc-in rooms [row col :visitable?] false) 127 | next-room (random-neighbor rooms row col)] 128 | (if next-room 129 | (let [rooms (tear-down-wall rooms row col (:row next-room) (:col next-room))] 130 | (loop [old-rooms rooms] 131 | (let [new-rooms (create-maze old-rooms (:row next-room) (:col next-room))] 132 | (if (= old-rooms new-rooms) 133 | old-rooms 134 | (recur new-rooms))))) 135 | rooms))) 136 | 137 | (let [rooms (create-maze rooms 0 0)] 138 | ; print top walls 139 | (doseq [row rooms] 140 | (print " _")) 141 | (println) 142 | ; print grid 143 | (doseq [row rooms] 144 | (print "|") 145 | (doseq [room row] 146 | (print (str (if (:bottom? room) "_" " ") 147 | (if (:right? room) "|" " ")))) 148 | (println))) 149 | ``` 150 | 151 | A to-do list: 152 | 153 | ```clojure 154 | (def to-dos (atom [])) 155 | 156 | (defn add-to-do [input] 157 | (reset! to-dos (conj (deref to-dos) input))) 158 | 159 | (println "Type a to-do and hit enter.") 160 | (loop [] 161 | (let [input (read-line)] 162 | (when (not= input "q") 163 | (add-to-do input) 164 | (recur)))) 165 | (println (deref to-dos)) 166 | ``` 167 | 168 | Barebones web app: 169 | 170 | ```clojure 171 | (ns example.server 172 | (:require [org.httpkit.server :as server] 173 | [hiccup.core :as h])) 174 | 175 | (defn app [request] 176 | {:status 200 177 | :body (h/html 178 | [:html 179 | [:body 180 | "Hello, world!"]])}) 181 | 182 | (defn -main [] 183 | (server/run-server (var app) {:port 3000})) 184 | ``` 185 | -------------------------------------------------------------------------------- /docs/paren-soup-light.css: -------------------------------------------------------------------------------- 1 | .paren-soup { 2 | font-size: 16px; 3 | font-family: monospace; 4 | color: gray; 5 | background-color: #f7f7f7; 6 | outline: 1px solid; 7 | caret-color: black; 8 | } 9 | 10 | .paren-soup .error-text { 11 | position: fixed; 12 | background-color: white; 13 | padding-left: 10px; 14 | } 15 | 16 | .paren-soup .instarepl { 17 | position: relative; 18 | min-width: 200px; 19 | max-width: 200px; 20 | } 21 | 22 | .paren-soup .instarepl .result { 23 | position: absolute; 24 | overflow: hidden; 25 | background-color: lightgreen; 26 | outline: 1px solid; 27 | max-width: inherit; 28 | word-wrap: break-word; 29 | right: 0px; 30 | opacity: 0.7; 31 | white-space: break-spaces; 32 | } 33 | 34 | .paren-soup .instarepl .result:hover { 35 | cursor: pointer; 36 | height: auto !important; 37 | z-index: 1; 38 | opacity: 1; 39 | } 40 | 41 | .paren-soup .instarepl .error { 42 | background-color: pink; 43 | } 44 | 45 | .paren-soup .numbers { 46 | float: left; 47 | padding: 0px 5px; 48 | text-align: right; 49 | opacity: 0.7; 50 | } 51 | 52 | .paren-soup .content { 53 | margin: 0px; 54 | margin-left: 200px; 55 | padding: 0px 5px; 56 | outline: 0px solid transparent; 57 | white-space: pre; 58 | word-wrap: normal; 59 | overflow: scroll; 60 | border-left: 1px solid #ddd; 61 | background-color: white; 62 | } 63 | 64 | .paren-soup .content .symbol { 65 | color: black; 66 | } 67 | 68 | .paren-soup .content .number { 69 | color: gold; 70 | } 71 | 72 | .paren-soup .content .string { 73 | color: red; 74 | } 75 | 76 | .paren-soup .content .keyword { 77 | color: blue; 78 | } 79 | 80 | .paren-soup .content .nil { 81 | color: lightblue; 82 | } 83 | 84 | .paren-soup .content .boolean { 85 | color: lightblue; 86 | } 87 | 88 | .paren-soup .content .error { 89 | display: none; 90 | width: 0.9em; 91 | height: 0.9em; 92 | background-color: red; 93 | border-radius: 0.3em; 94 | } 95 | 96 | .paren-soup .content .rainbow-0 { 97 | color: gray; 98 | } 99 | 100 | .paren-soup .content .rainbow-1 { 101 | color: gray; 102 | } 103 | 104 | .paren-soup .content .rainbow-2 { 105 | color: gray; 106 | } 107 | 108 | .paren-soup .content .rainbow-3 { 109 | color: gray; 110 | } 111 | 112 | .paren-soup .content .rainbow-4 { 113 | color: gray; 114 | } 115 | -------------------------------------------------------------------------------- /docs/people.csv: -------------------------------------------------------------------------------- 1 | id,first_name,last_name,email,country,ip_address 2 | 1,Martha,Jenkins,mjenkins0@un.org,France,97.252.235.143 3 | 2,Kathleen,Medina,kmedina1@unc.edu,Czech Republic,149.62.63.167 4 | 3,Earl,Murray,emurray2@va.gov,Poland,242.239.132.230 5 | 4,Teresa,Fox,tfox3@webnode.com,Russia,136.0.65.186 6 | 5,Carolyn,West,cwest4@admin.ch,Nigeria,240.149.227.109 7 | 6,Marie,Lawson,mlawson5@youtube.com,China,238.101.184.197 8 | 7,Eugene,Stone,estone6@nature.com,Colombia,55.107.194.185 9 | 8,Nicholas,Oliver,noliver7@slate.com,China,159.45.204.128 10 | 9,Carol,Flores,cflores8@reddit.com,Russia,62.117.22.131 11 | 10,Nicole,James,njames9@imageshack.us,United States,147.13.140.165 12 | 11,Gary,Murphy,gmurphya@dailymail.co.uk,Portugal,51.53.42.210 13 | 12,Jonathan,Stephens,jstephensb@angelfire.com,Sweden,209.33.35.62 14 | 13,Anthony,Elliott,aelliottc@psu.edu,Colombia,27.186.118.148 15 | 14,Todd,West,twestd@ifeng.com,Indonesia,56.181.227.119 16 | 15,Brandon,Fields,bfieldse@google.com.br,China,14.72.89.67 17 | 16,Jennifer,Sullivan,jsullivanf@live.com,Egypt,153.57.254.49 18 | 17,Diane,Sims,dsimsg@nytimes.com,China,48.226.245.111 19 | 18,William,Jackson,wjacksonh@shinystat.com,Macedonia,52.113.45.192 20 | 19,Christopher,Little,clittlei@163.com,Indonesia,210.204.226.20 21 | 20,Rebecca,Hudson,rhudsonj@goo.ne.jp,Japan,177.144.146.88 22 | 21,Carol,Henderson,chendersonk@homestead.com,China,83.177.157.141 23 | 22,Walter,Barnes,wbarnesl@hibu.com,Sweden,22.246.183.119 24 | 23,Phillip,Rodriguez,prodriguezm@over-blog.com,Paraguay,125.171.249.85 25 | 24,Benjamin,Duncan,bduncann@state.gov,Philippines,15.254.166.186 26 | 25,Cheryl,Jackson,cjacksono@wp.com,China,127.225.15.214 27 | 26,Dennis,Warren,dwarrenp@yahoo.co.jp,Canada,153.31.84.119 28 | 27,Lillian,Pierce,lpierceq@storify.com,Portugal,189.223.235.2 29 | 28,Heather,Evans,hevansr@mit.edu,China,53.8.251.213 30 | 29,Lawrence,Dixon,ldixons@youku.com,Brazil,197.62.211.10 31 | 30,Heather,Ruiz,hruizt@free.fr,Japan,218.150.32.122 32 | 31,Cheryl,Rose,croseu@edublogs.org,Indonesia,23.8.100.251 33 | 32,Joan,Mendoza,jmendozav@ibm.com,China,207.156.169.16 34 | 33,Stephen,Mills,smillsw@ebay.com,Nigeria,158.207.253.54 35 | 34,Fred,Weaver,fweaverx@irs.gov,China,255.252.2.8 36 | 35,Laura,Walker,lwalkery@dyndns.org,Ivory Coast,1.38.233.97 37 | 36,Janet,Carr,jcarrz@people.com.cn,Belarus,87.2.137.196 38 | 37,Matthew,Thomas,mthomas10@apache.org,China,87.239.205.41 39 | 38,Karen,Fuller,kfuller11@biglobe.ne.jp,Philippines,41.68.238.121 40 | 39,Virginia,Day,vday12@google.com.br,Slovenia,55.13.68.132 41 | 40,Kenneth,Parker,kparker13@devhub.com,Russia,253.55.38.245 42 | 41,Chris,Day,cday14@stumbleupon.com,Guadeloupe,240.228.77.72 43 | 42,Theresa,Anderson,tanderson15@seattletimes.com,China,97.223.21.68 44 | 43,Daniel,Jacobs,djacobs16@mozilla.com,Indonesia,114.96.8.128 45 | 44,Henry,Long,hlong17@sohu.com,Greece,45.215.93.102 46 | 45,Karen,Fields,kfields18@pinterest.com,Botswana,83.216.152.226 47 | 46,Annie,Young,ayoung19@sphinn.com,France,85.39.109.220 48 | 47,Jennifer,Sanders,jsanders1a@pinterest.com,China,176.179.109.6 49 | 48,Tina,Richards,trichards1b@artisteer.com,Indonesia,240.135.242.231 50 | 49,Russell,Ellis,rellis1c@earthlink.net,Morocco,8.44.24.43 51 | 50,Dorothy,Patterson,dpatterson1d@google.it,Vietnam,187.113.101.114 52 | 51,Brian,Ray,bray1e@wp.com,Japan,25.220.166.185 53 | 52,Eric,Bryant,ebryant1f@adobe.com,Philippines,99.40.148.116 54 | 53,Roger,Sanchez,rsanchez1g@edublogs.org,Russia,210.3.224.158 55 | 54,Lori,Austin,laustin1h@elpais.com,Nigeria,83.195.145.206 56 | 55,Barbara,Washington,bwashington1i@digg.com,China,241.239.248.7 57 | 56,Thomas,Green,tgreen1j@etsy.com,Philippines,206.171.156.162 58 | 57,Evelyn,Jackson,ejackson1k@technorati.com,Sweden,93.53.27.253 59 | 58,Catherine,Harvey,charvey1l@un.org,China,75.71.219.250 60 | 59,Debra,Wilson,dwilson1m@hp.com,Marshall Islands,104.136.254.17 61 | 60,Gregory,Garcia,ggarcia1n@unblog.fr,Portugal,82.150.14.212 62 | 61,Irene,Turner,iturner1o@mit.edu,Russia,72.160.29.127 63 | 62,Donna,Palmer,dpalmer1p@newsvine.com,China,30.245.87.42 64 | 63,Carolyn,Dunn,cdunn1q@nhs.uk,Czech Republic,155.176.201.243 65 | 64,Alan,Austin,aaustin1r@dailymail.co.uk,Peru,59.120.155.55 66 | 65,Frances,Owens,fowens1s@jimdo.com,Brazil,53.170.214.36 67 | 66,Pamela,Moore,pmoore1t@odnoklassniki.ru,Russia,99.236.60.25 68 | 67,Albert,West,awest1u@tripadvisor.com,Philippines,230.139.203.73 69 | 68,Bonnie,Mitchell,bmitchell1v@netscape.com,Indonesia,54.165.176.179 70 | 69,Eric,Williams,ewilliams1w@ebay.co.uk,Canada,116.87.204.114 71 | 70,Phillip,Powell,ppowell1x@ihg.com,China,213.4.123.36 72 | 71,Diane,Hudson,dhudson1y@usa.gov,Croatia,236.203.207.43 73 | 72,Charles,Evans,cevans1z@barnesandnoble.com,Canada,245.247.213.5 74 | 73,Andrea,Anderson,aanderson20@gizmodo.com,Brazil,97.118.157.115 75 | 74,Ashley,Green,agreen21@booking.com,China,161.180.162.159 76 | 75,Jesse,Thompson,jthompson22@mapy.cz,Indonesia,208.163.16.46 77 | 76,Lawrence,Warren,lwarren23@foxnews.com,Mali,138.47.106.220 78 | 77,Juan,Lewis,jlewis24@csmonitor.com,Brazil,55.30.25.254 79 | 78,Steven,Hudson,shudson25@nba.com,China,75.120.128.249 80 | 79,Antonio,Day,aday26@omniture.com,China,108.142.133.98 81 | 80,Michelle,Walker,mwalker27@opensource.org,United States,103.189.76.180 82 | 81,Victor,Lopez,vlopez28@bandcamp.com,Paraguay,72.72.45.71 83 | 82,Paula,Fuller,pfuller29@msn.com,Philippines,130.1.53.207 84 | 83,Lori,Griffin,lgriffin2a@marketwatch.com,Sweden,217.241.64.240 85 | 84,Cynthia,Gardner,cgardner2b@sitemeter.com,Pakistan,163.93.171.48 86 | 85,Thomas,Matthews,tmatthews2c@hubpages.com,Colombia,172.24.45.162 87 | 86,Adam,Powell,apowell2d@xing.com,Botswana,87.196.53.169 88 | 87,Marilyn,Evans,mevans2e@yellowbook.com,Canada,238.112.153.1 89 | 88,Samuel,Parker,sparker2f@discuz.net,China,18.212.48.12 90 | 89,Kelly,Mendoza,kmendoza2g@umn.edu,Brazil,115.238.121.145 91 | 90,Ryan,Flores,rflores2h@bluehost.com,Indonesia,223.174.148.129 92 | 91,Angela,George,ageorge2i@columbia.edu,U.S. Virgin Islands,113.154.7.134 93 | 92,Lillian,Morris,lmorris2j@cyberchimps.com,Dominican Republic,224.68.103.4 94 | 93,Willie,Harrison,wharrison2k@oakley.com,Nigeria,59.75.8.206 95 | 94,Sharon,Boyd,sboyd2l@github.io,Honduras,161.22.142.125 96 | 95,Barbara,Wallace,bwallace2m@php.net,China,149.232.81.225 97 | 96,Donna,Murphy,dmurphy2n@studiopress.com,Ukraine,17.248.165.20 98 | 97,Carl,Gilbert,cgilbert2o@adobe.com,Thailand,127.217.160.149 99 | 98,Jean,Wells,jwells2p@eepurl.com,Finland,230.235.200.68 100 | 99,Jeremy,Reid,jreid2q@gmpg.org,Czech Republic,20.220.95.102 101 | 100,Carol,Knight,cknight2r@oakley.com,China,241.88.169.101 102 | 101,Anthony,Hicks,ahicks2s@usda.gov,China,80.155.105.159 103 | 102,Wayne,Miller,wmiller2t@facebook.com,Indonesia,184.155.50.3 104 | 103,Stephen,Johnson,sjohnson2u@nationalgeographic.com,Philippines,213.60.10.78 105 | 104,Jane,Castillo,jcastillo2v@wisc.edu,Indonesia,80.107.75.180 106 | 105,Jean,Bryant,jbryant2w@pinterest.com,Indonesia,137.72.185.197 107 | 106,Sara,Weaver,sweaver2x@howstuffworks.com,China,182.25.132.120 108 | 107,Victor,Riley,vriley2y@intel.com,Thailand,4.100.152.191 109 | 108,Timothy,Bradley,tbradley2z@virginia.edu,Syria,119.225.112.135 110 | 109,Catherine,Hawkins,chawkins30@squidoo.com,Portugal,171.239.27.135 111 | 110,Karen,Hawkins,khawkins31@ftc.gov,Portugal,33.56.66.255 112 | 111,Bonnie,King,bking32@cpanel.net,Slovenia,102.173.11.212 113 | 112,Raymond,Franklin,rfranklin33@prnewswire.com,Indonesia,119.126.111.128 114 | 113,Nancy,Wells,nwells34@ycombinator.com,Barbados,79.250.18.179 115 | 114,Bruce,Vasquez,bvasquez35@nba.com,Belarus,189.17.5.180 116 | 115,Kimberly,Kennedy,kkennedy36@mac.com,Peru,176.194.81.209 117 | 116,Donald,Harrison,dharrison37@home.pl,China,128.200.38.242 118 | 117,Marilyn,Carr,mcarr38@pinterest.com,Russia,4.218.44.115 119 | 118,Brenda,Graham,bgraham39@geocities.jp,Philippines,229.116.222.114 120 | 119,Kathy,Carroll,kcarroll3a@blogtalkradio.com,China,203.233.235.172 121 | 120,Carol,Hill,chill3b@aol.com,China,113.114.247.143 122 | 121,Ronald,Scott,rscott3c@gmpg.org,Indonesia,30.252.222.186 123 | 122,Jose,Foster,jfoster3d@wikia.com,France,63.83.227.143 124 | 123,Diana,Watson,dwatson3e@naver.com,Nigeria,103.198.63.55 125 | 124,Melissa,Carr,mcarr3f@biblegateway.com,North Korea,59.124.93.104 126 | 125,Denise,Lee,dlee3g@rambler.ru,China,202.201.183.132 127 | 126,Justin,Murphy,jmurphy3h@liveinternet.ru,Indonesia,142.26.16.209 128 | 127,Patricia,Schmidt,pschmidt3i@ted.com,Sweden,145.78.209.142 129 | 128,Harold,Price,hprice3j@nature.com,Indonesia,68.75.246.180 130 | 129,Beverly,Martinez,bmartinez3k@dropbox.com,Russia,60.87.206.228 131 | 130,Ryan,Morgan,rmorgan3l@marketwatch.com,Philippines,1.120.255.232 132 | 131,Donna,Kelly,dkelly3m@intel.com,Guatemala,164.24.46.142 133 | 132,Amy,Rogers,arogers3n@yale.edu,Sweden,94.111.74.116 134 | 133,Kevin,Sims,ksims3o@facebook.com,Vietnam,76.215.116.59 135 | 134,Louis,Boyd,lboyd3p@sphinn.com,Saudi Arabia,212.36.195.135 136 | 135,Betty,West,bwest3q@jigsy.com,China,57.178.4.84 137 | 136,Janice,Ortiz,jortiz3r@rediff.com,China,133.93.66.127 138 | 137,Andrew,Mason,amason3s@cafepress.com,Serbia,187.239.139.16 139 | 138,Ronald,Chavez,rchavez3t@hc360.com,China,227.99.101.33 140 | 139,Jessica,Walker,jwalker3u@wordpress.org,Liberia,36.95.253.110 141 | 140,Carol,Stewart,cstewart3v@youku.com,Russia,125.101.246.37 142 | 141,Matthew,Morris,mmorris3w@xing.com,Peru,23.213.40.253 143 | 142,Rachel,Adams,radams3x@mail.ru,Azerbaijan,92.27.158.111 144 | 143,Phyllis,Rogers,progers3y@army.mil,France,128.42.201.111 145 | 144,Jason,Perkins,jperkins3z@comcast.net,Indonesia,236.58.201.145 146 | 145,Stephen,Peters,speters40@arizona.edu,Slovenia,155.19.59.116 147 | 146,Irene,Carr,icarr41@hhs.gov,Guatemala,86.221.153.245 148 | 147,Susan,Hudson,shudson42@skype.com,Philippines,151.63.71.34 149 | 148,Ronald,Daniels,rdaniels43@unc.edu,China,206.39.72.223 150 | 149,Stephanie,Hart,shart44@ocn.ne.jp,Brazil,3.31.103.123 151 | 150,Jonathan,Lopez,jlopez45@privacy.gov.au,Vietnam,231.60.93.185 152 | 151,Jennifer,Stanley,jstanley46@cbslocal.com,Sweden,202.251.251.199 153 | 152,Christina,Gonzales,cgonzales47@discovery.com,Indonesia,151.253.210.64 154 | 153,Peter,Morales,pmorales48@ftc.gov,Philippines,5.218.104.140 155 | 154,Lisa,Stephens,lstephens49@wikispaces.com,Argentina,197.68.57.123 156 | 155,Kathy,Baker,kbaker4a@dagondesign.com,Mexico,18.208.57.130 157 | 156,Edward,Carr,ecarr4b@youku.com,Indonesia,129.238.244.222 158 | 157,Anna,Hernandez,ahernandez4c@github.com,Indonesia,138.248.95.94 159 | 158,Anna,Rogers,arogers4d@angelfire.com,Russia,66.152.135.204 160 | 159,Matthew,Olson,molson4e@google.com,France,114.137.112.208 161 | 160,Nicole,King,nking4f@mysql.com,Philippines,123.71.223.239 162 | 161,Russell,Hernandez,rhernandez4g@ucoz.ru,Sweden,31.222.172.203 163 | 162,Phyllis,Oliver,poliver4h@t.co,Cuba,237.86.45.166 164 | 163,Peter,Wells,pwells4i@washingtonpost.com,Nigeria,2.58.101.245 165 | 164,Victor,Henry,vhenry4j@techcrunch.com,Sweden,162.17.25.164 166 | 165,Paula,Robinson,probinson4k@parallels.com,Sweden,194.126.176.2 167 | 166,Cynthia,Torres,ctorres4l@usa.gov,Japan,71.199.109.20 168 | 167,Brenda,Jordan,bjordan4m@oakley.com,Indonesia,112.221.217.19 169 | 168,Donald,Owens,dowens4n@digg.com,United States,138.20.225.245 170 | 169,Kathleen,Gardner,kgardner4o@springer.com,Indonesia,145.167.38.22 171 | 170,Earl,Diaz,ediaz4p@163.com,Indonesia,90.122.69.136 172 | 171,Scott,Stevens,sstevens4q@sciencedirect.com,Thailand,154.73.218.106 173 | 172,Lillian,Ferguson,lferguson4r@clickbank.net,Venezuela,147.127.99.170 174 | 173,Joan,Sanchez,jsanchez4s@accuweather.com,Germany,183.68.219.29 175 | 174,Albert,Howell,ahowell4t@stanford.edu,Honduras,50.255.169.212 176 | 175,Howard,King,hking4u@bing.com,United States,233.25.107.143 177 | 176,Fred,Howard,fhoward4v@mtv.com,China,42.8.206.148 178 | 177,Anna,Price,aprice4w@flickr.com,France,139.88.178.176 179 | 178,Stephen,Cook,scook4x@webeden.co.uk,Tajikistan,169.94.163.255 180 | 179,Aaron,Watson,awatson4y@over-blog.com,Poland,127.153.9.104 181 | 180,Bobby,Jones,bjones4z@sciencedaily.com,France,137.17.11.176 182 | 181,Clarence,Banks,cbanks50@nifty.com,Indonesia,238.164.213.150 183 | 182,Julia,Holmes,jholmes51@paypal.com,China,171.150.182.204 184 | 183,Nicole,Rose,nrose52@mozilla.org,Venezuela,76.145.152.20 185 | 184,Kathryn,Wells,kwells53@cnbc.com,Philippines,1.65.214.187 186 | 185,Walter,Rodriguez,wrodriguez54@acquirethisname.com,Indonesia,75.161.20.102 187 | 186,Laura,Williams,lwilliams55@paypal.com,Thailand,217.240.151.206 188 | 187,Denise,Lee,dlee56@1688.com,France,21.68.46.58 189 | 188,Philip,Perkins,pperkins57@live.com,Indonesia,243.106.39.238 190 | 189,Alan,Carpenter,acarpenter58@newsvine.com,Norway,227.194.121.90 191 | 190,Irene,Fox,ifox59@accuweather.com,Lebanon,89.192.213.188 192 | 191,Michelle,Bradley,mbradley5a@naver.com,Luxembourg,188.253.110.23 193 | 192,Jane,Sims,jsims5b@ca.gov,Thailand,144.193.103.27 194 | 193,Christopher,Kelly,ckelly5c@vk.com,United States,222.52.213.79 195 | 194,Denise,Welch,dwelch5d@google.com.hk,Nigeria,154.171.118.30 196 | 195,Harry,Rice,hrice5e@archive.org,China,241.76.154.28 197 | 196,Jennifer,Morris,jmorris5f@wsj.com,Colombia,253.31.74.84 198 | 197,Shirley,Lawrence,slawrence5g@fda.gov,Philippines,118.133.161.62 199 | 198,Jean,Reed,jreed5h@jalbum.net,Norway,104.31.119.29 200 | 199,Ryan,Armstrong,rarmstrong5i@quantcast.com,Sudan,107.100.84.181 201 | 200,Angela,Johnston,ajohnston5j@blog.com,Brazil,98.133.10.86 202 | 201,Louise,Perez,lperez5k@amazon.de,Thailand,138.247.176.238 203 | 202,Joyce,Spencer,jspencer5l@berkeley.edu,Brazil,162.136.33.98 204 | 203,Susan,Jenkins,sjenkins5m@ask.com,Chile,17.120.234.86 205 | 204,Jesse,Wagner,jwagner5n@dion.ne.jp,China,126.65.21.54 206 | 205,Kathleen,Lewis,klewis5o@icio.us,Japan,116.234.101.225 207 | 206,George,West,gwest5p@rambler.ru,Japan,228.126.28.12 208 | 207,Cheryl,Brown,cbrown5q@techcrunch.com,Czech Republic,200.123.159.33 209 | 208,Anne,Henderson,ahenderson5r@wordpress.org,Russia,247.234.106.116 210 | 209,Christina,Howell,chowell5s@discovery.com,Slovenia,104.56.177.56 211 | 210,Diana,Brooks,dbrooks5t@netscape.com,China,157.56.154.72 212 | 211,Albert,Williamson,awilliamson5u@aol.com,Philippines,187.134.179.207 213 | 212,William,Wallace,wwallace5v@icio.us,Peru,207.111.153.127 214 | 213,Mildred,Alexander,malexander5w@time.com,Russia,159.148.232.130 215 | 214,Christine,Payne,cpayne5x@sourceforge.net,China,231.45.84.115 216 | 215,Raymond,Snyder,rsnyder5y@virginia.edu,Japan,89.33.26.73 217 | 216,Lisa,Hamilton,lhamilton5z@facebook.com,China,170.244.226.169 218 | 217,Kenneth,Boyd,kboyd60@domainmarket.com,Russia,230.219.116.165 219 | 218,Andrea,Harvey,aharvey61@t.co,China,97.184.133.129 220 | 219,Douglas,Hernandez,dhernandez62@sourceforge.net,Portugal,172.233.235.93 221 | 220,Irene,Long,ilong63@timesonline.co.uk,Indonesia,144.140.158.65 222 | 221,Ruby,Robertson,rrobertson64@aboutads.info,China,47.111.144.139 223 | 222,Jacqueline,Thomas,jthomas65@creativecommons.org,Latvia,231.214.160.59 224 | 223,Walter,Evans,wevans66@arizona.edu,China,57.189.221.233 225 | 224,Ruby,Bryant,rbryant67@jiathis.com,Indonesia,15.21.141.200 226 | 225,David,Porter,dporter68@tiny.cc,China,112.22.182.112 227 | 226,Gregory,Olson,golson69@google.cn,China,84.133.10.151 228 | 227,Judith,Carter,jcarter6a@ameblo.jp,Russia,95.224.192.80 229 | 228,Joan,Hunter,jhunter6b@virginia.edu,China,89.86.60.72 230 | 229,Sara,Long,slong6c@forbes.com,Malta,120.202.137.251 231 | 230,Jose,Brooks,jbrooks6d@sitemeter.com,Poland,207.187.105.204 232 | 231,Jimmy,Kelley,jkelley6e@nasa.gov,Armenia,206.254.34.93 233 | 232,Brian,Griffin,bgriffin6f@seesaa.net,China,201.230.191.97 234 | 233,Johnny,Wheeler,jwheeler6g@hhs.gov,Mexico,40.102.239.175 235 | 234,Martin,Morales,mmorales6h@chicagotribune.com,France,140.31.100.140 236 | 235,Julia,Mcdonald,jmcdonald6i@mail.ru,Kuwait,114.246.226.95 237 | 236,Juan,Howell,jhowell6j@mapy.cz,China,228.196.116.99 238 | 237,Irene,Alexander,ialexander6k@flickr.com,Russia,48.221.74.240 239 | 238,Carl,Rivera,crivera6l@comcast.net,Brazil,105.223.92.106 240 | 239,Lori,Fisher,lfisher6m@geocities.com,Russia,239.58.227.0 241 | 240,Christina,Ferguson,cferguson6n@tamu.edu,China,150.105.143.203 242 | 241,Stephen,Hawkins,shawkins6o@behance.net,Indonesia,55.78.72.99 243 | 242,Russell,Rose,rrose6p@sbwire.com,Brazil,213.117.112.156 244 | 243,Denise,Alexander,dalexander6q@blogs.com,Philippines,239.217.118.232 245 | 244,Ralph,Harvey,rharvey6r@oakley.com,France,22.132.163.116 246 | 245,Todd,Torres,ttorres6s@google.es,Ukraine,215.117.182.141 247 | 246,Catherine,Ryan,cryan6t@wikimedia.org,Germany,23.223.71.147 248 | 247,Irene,Carpenter,icarpenter6u@51.la,Czech Republic,112.45.187.218 249 | 248,Philip,Mcdonald,pmcdonald6v@bloglovin.com,Indonesia,91.82.41.207 250 | 249,Janice,Ray,jray6w@tuttocitta.it,China,111.234.202.181 251 | 250,Jonathan,Wood,jwood6x@friendfeed.com,Spain,165.60.128.179 252 | 251,Ashley,Fowler,afowler6y@simplemachines.org,Indonesia,202.211.108.184 253 | 252,Peter,Palmer,ppalmer6z@is.gd,France,164.238.73.91 254 | 253,Sean,West,swest70@dailymail.co.uk,Poland,232.41.22.61 255 | 254,Laura,Powell,lpowell71@cbslocal.com,Philippines,39.61.21.71 256 | 255,Edward,Stewart,estewart72@columbia.edu,China,130.13.238.179 257 | 256,Angela,Henry,ahenry73@pcworld.com,Ethiopia,133.187.23.83 258 | 257,Nicholas,Stone,nstone74@hao123.com,Ukraine,156.152.126.250 259 | 258,Martin,Pierce,mpierce75@wix.com,Russia,209.14.21.76 260 | 259,Teresa,Fowler,tfowler76@google.pl,Albania,102.95.108.170 261 | 260,Scott,Rodriguez,srodriguez77@cnet.com,Brazil,226.232.79.131 262 | 261,Mary,Smith,msmith78@cbslocal.com,Slovenia,126.139.57.181 263 | 262,Emily,Franklin,efranklin79@tuttocitta.it,Czech Republic,229.217.142.152 264 | 263,Ruth,Jordan,rjordan7a@marketwatch.com,Indonesia,159.187.168.142 265 | 264,Willie,Grant,wgrant7b@opera.com,Japan,82.202.41.105 266 | 265,Doris,Stephens,dstephens7c@acquirethisname.com,Norway,29.254.22.225 267 | 266,Jose,Banks,jbanks7d@mit.edu,Poland,45.102.134.204 268 | 267,Ruth,Wilson,rwilson7e@google.cn,Poland,159.127.94.89 269 | 268,Paula,Wilson,pwilson7f@mapy.cz,France,208.65.171.210 270 | 269,Brandon,Richards,brichards7g@nydailynews.com,Peru,100.236.73.52 271 | 270,Steven,Green,sgreen7h@cbslocal.com,France,51.166.146.5 272 | 271,John,Brown,jbrown7i@merriam-webster.com,Sweden,23.98.44.154 273 | 272,Gloria,Mills,gmills7j@state.gov,Yemen,138.138.185.111 274 | 273,Denise,Flores,dflores7k@accuweather.com,Croatia,122.87.196.233 275 | 274,Evelyn,Morrison,emorrison7l@timesonline.co.uk,Russia,40.186.92.158 276 | 275,Phyllis,Gilbert,pgilbert7m@telegraph.co.uk,Honduras,155.117.26.181 277 | 276,Raymond,Kennedy,rkennedy7n@army.mil,France,24.242.118.214 278 | 277,George,Lee,glee7o@ftc.gov,Germany,87.43.19.62 279 | 278,Bonnie,Harrison,bharrison7p@geocities.jp,Russia,74.223.18.174 280 | 279,Sean,Sims,ssims7q@businesswire.com,Czech Republic,186.28.98.67 281 | 280,Walter,Parker,wparker7r@wsj.com,Iran,160.251.154.175 282 | 281,Douglas,Griffin,dgriffin7s@dion.ne.jp,Ukraine,34.176.49.225 283 | 282,Ruth,Hill,rhill7t@mashable.com,Croatia,106.231.226.177 284 | 283,Frances,George,fgeorge7u@google.ru,"Bonaire, Saint Eustatius and Saba ",58.118.53.170 285 | 284,Martha,Sanchez,msanchez7v@ucoz.com,Colombia,17.22.220.222 286 | 285,Alan,Green,agreen7w@marriott.com,Colombia,197.118.82.18 287 | 286,Gary,Reed,greed7x@creativecommons.org,Israel,133.26.199.195 288 | 287,Jimmy,Frazier,jfrazier7y@icq.com,Russia,9.133.9.206 289 | 288,Patrick,Ellis,pellis7z@ameblo.jp,China,225.191.236.238 290 | 289,Janet,Jenkins,jjenkins80@opensource.org,China,21.227.48.172 291 | 290,Jerry,Evans,jevans81@angelfire.com,Armenia,71.101.211.88 292 | 291,Anthony,Richardson,arichardson82@google.co.uk,Russia,209.91.27.138 293 | 292,Steven,Stanley,sstanley83@nyu.edu,Brazil,125.231.206.151 294 | 293,Joyce,Burton,jburton84@nbcnews.com,Russia,225.136.190.85 295 | 294,Willie,Garrett,wgarrett85@github.io,Jordan,254.103.197.192 296 | 295,Elizabeth,Harrison,eharrison86@seesaa.net,Sweden,29.254.185.226 297 | 296,Jason,Evans,jevans87@digg.com,Greece,193.33.17.210 298 | 297,Amanda,Matthews,amatthews88@arizona.edu,Poland,133.43.183.1 299 | 298,Beverly,Phillips,bphillips89@wikimedia.org,Indonesia,4.239.48.210 300 | 299,Joshua,Chapman,jchapman8a@domainmarket.com,Philippines,235.123.109.226 301 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: lightgray; 3 | font-family: Helvetica, Arial; 4 | color: dimgray; 5 | border: none; 6 | width: 600px; 7 | margin: auto; 8 | margin-top: 50px; 9 | margin-bottom: 100px; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /exercises/cards.clj: -------------------------------------------------------------------------------- 1 | (def suits [:spades :hearts :clubs :diamonds]) 2 | (def ranks (range 1 14)) 3 | 4 | (defrecord Card [suit rank]) 5 | 6 | (def deck 7 | (vec 8 | (for [suit suits 9 | rank ranks] 10 | (map->Card 11 | {:suit suit 12 | :rank rank})))) 13 | 14 | (comment 15 | (filter 16 | (fn [card] 17 | (= (:suit card) :clubs)) 18 | deck) 19 | 20 | (map :suit deck)) 21 | 22 | (defonce hands 23 | (for [index1 (range 0 (dec (count deck))) 24 | index2 (range (inc index1) (count deck)) 25 | index3 (range (inc index2) (count deck)) 26 | index4 (range (inc index3) (count deck))] 27 | #{(get deck index1) 28 | (get deck index2) 29 | (get deck index3) 30 | (get deck index4)})) 31 | 32 | (comment 33 | (defonce hands 34 | (set 35 | (for [card1 deck 36 | card2 (disj deck card1) 37 | card3 (disj deck card1 card2) 38 | card4 (disj deck card1 card2 card3)] 39 | #{card1 card2 card3 card4})))) 40 | 41 | (defn flush? [hand] 42 | (= 1 (count (set (map :suit hand))))) 43 | 44 | (defn straight? [hand] 45 | (let [ranks (sort (map :rank hand)) 46 | [r1 _ _ r4] ranks] 47 | (and (= 3 (- r4 r1)) 48 | (= 4 (count (set ranks)))))) 49 | 50 | (defn straight-flush? [hand] 51 | (and (flush? hand) 52 | (straight? hand))) 53 | 54 | (comment 55 | (defn two-pair? [hand] 56 | (let [groups (group-by :rank hand) 57 | ranks (keys groups) 58 | [r1 r2] ranks] 59 | (and (= 2 (count ranks)) 60 | (= 2 (count (get groups r1))))))) 61 | 62 | (defn two-pair? [hand] 63 | (let [ranks (sort (map :rank hand)) 64 | [r1 r2 r3 r4] ranks] 65 | (and (= r1 r2) 66 | (= r3 r4) 67 | (not= r2 r3)))) 68 | 69 | (defn three-of-a-kind? [hand] 70 | (let [groups (group-by :rank hand)] 71 | (some (fn [[_ cards]] 72 | (= 3 (count cards))) 73 | groups))) 74 | 75 | (count (filter three-of-a-kind? hands)) 76 | 77 | -------------------------------------------------------------------------------- /exercises/maze.clj: -------------------------------------------------------------------------------- 1 | (def size 10) 2 | 3 | (defrecord Room [row col bottom? right? 4 | can-visit?]) 5 | 6 | (def rooms 7 | (vec 8 | (for [row (range 0 size)] 9 | (vec 10 | (for [col (range 0 size)] 11 | (map->Room 12 | {:row row 13 | :col col 14 | :bottom? true 15 | :right? true 16 | :can-visit? true})))))) 17 | 18 | (defn get-neighbors [rooms row col] 19 | [(get-in rooms [row (dec col)]) 20 | (get-in rooms [row (inc col)]) 21 | (get-in rooms [(dec row) col]) 22 | (get-in rooms [(inc row) col])]) 23 | 24 | (defn get-random-neighbor [rooms row col] 25 | (let [neighbors (get-neighbors rooms row col) 26 | neighbors (filter :can-visit? neighbors)] 27 | (if (empty? neighbors) 28 | nil 29 | (rand-nth neighbors)))) 30 | 31 | (defn tear-down-wall [rooms room1 room2] 32 | (let [row1 (:row room1) 33 | col1 (:col room1) 34 | row2 (:row room2) 35 | col2 (:col room2)] 36 | (cond 37 | (< col2 col1) 38 | (assoc-in rooms [row1 col2 :right?] false) 39 | 40 | (> col2 col1) 41 | (assoc-in rooms [row1 col1 :right?] false) 42 | 43 | (> row2 row1) 44 | (assoc-in rooms [row1 col1 :bottom?] false) 45 | 46 | (< row2 row1) 47 | (assoc-in rooms [row2 col1 :bottom?] false)))) 48 | 49 | (defn make-path [rooms row col] 50 | (let [neighbor (get-random-neighbor rooms row col) 51 | rooms (assoc-in rooms [row col :can-visit?] false)] 52 | (if neighbor 53 | (let [rooms (tear-down-wall rooms 54 | {:row row :col col} 55 | neighbor)] 56 | (loop [old-rooms rooms] 57 | (let [rooms (make-path old-rooms (:row neighbor) (:col neighbor))] 58 | (if (= old-rooms rooms) 59 | rooms 60 | (recur rooms))))) 61 | rooms))) 62 | 63 | (def rooms (make-path rooms 0 0)) 64 | 65 | (dotimes [_ size] 66 | (print " _")) 67 | (println) 68 | (doseq [row rooms] 69 | (print "|") 70 | (doseq [room row] 71 | (print 72 | (str 73 | (if (:bottom? room) "_" " ") 74 | (if (:right? room) "|" " ")))) 75 | (println)) 76 | 77 | -------------------------------------------------------------------------------- /exercises/people.clj: -------------------------------------------------------------------------------- 1 | (require '[clojure.string :as str]) 2 | 3 | (let [people (slurp "docs/people.csv") 4 | people (str/split-lines people) 5 | header (first people) 6 | people (rest people) 7 | header (str/split header #",") 8 | header (map keyword header) 9 | people (map 10 | (fn [person] 11 | (zipmap header (str/split person #","))) 12 | people) 13 | people (filter 14 | (fn [person] 15 | (= (:country person) 16 | "Philippines")) 17 | people)] 18 | people) 19 | 20 | (as-> (slurp "docs/people.csv") 21 | people 22 | (str/split-lines people) 23 | (map 24 | (fn [person] 25 | (let [header (first people) 26 | header (str/split header #",") 27 | header (map keyword header) 28 | person (str/split person #",")] 29 | (zipmap header person))) 30 | (rest people))) 31 | 32 | -------------------------------------------------------------------------------- /exercises/read.clj: -------------------------------------------------------------------------------- 1 | (def text "35 1/2 1.2 foo-bar") 2 | 3 | (def chars (vec text)) 4 | 5 | (def digits (set "0123456789")) 6 | 7 | (defn digit? [ch] 8 | (contains? digits ch)) 9 | 10 | (def char-names {\space :space 11 | \. :period 12 | \/ :forward-slash}) 13 | 14 | (defn char-type [ch] 15 | (cond 16 | (digit? ch) 17 | :digit 18 | 19 | (contains? char-names ch) 20 | (get char-names ch) 21 | 22 | true 23 | :other)) 24 | 25 | (let [first-char (get text 0) 26 | second-char (get text 1) 27 | third-char (get text 2)] 28 | (and (digit? first-char) 29 | (digit? second-char) 30 | (digit? third-char))) 31 | 32 | (loop [index 0 33 | result [] 34 | group [] 35 | last-type nil] 36 | (let [ch (get chars index) 37 | current-type (char-type ch)] 38 | (if ch 39 | (if (= current-type 40 | (or last-type current-type)) 41 | (recur 42 | (+ index 1) 43 | result 44 | (conj group ch) 45 | current-type) 46 | (recur 47 | (+ index 1) 48 | (conj result group) 49 | [ch] 50 | current-type)) 51 | (conj result group)))) 52 | 53 | (defn read-token [text] 54 | (or (re-find #"^\d+\/\d+" text) 55 | (re-find #"^\d+\.\d+" text) 56 | (re-find #"^\d+" text) 57 | (re-find #"^ +" text) 58 | (re-find #"^[^\d].*" text))) 59 | 60 | (loop [result [] 61 | index 0] 62 | (let [text (subs text index) 63 | token (read-token text)] 64 | (if token 65 | (recur 66 | (conj result token) 67 | (+ index (count token))) 68 | result))) 69 | 70 | -------------------------------------------------------------------------------- /exercises/shape.clj: -------------------------------------------------------------------------------- 1 | (defrecord Circle [radius]) 2 | (defrecord Square [edge]) 3 | 4 | (defprotocol Area 5 | (area [this])) 6 | 7 | (extend-protocol Area 8 | Circle 9 | (area [{:keys [radius]}] 10 | (* Math/PI radius radius)) 11 | 12 | Square 13 | (area [{:keys [edge]}] 14 | (* edge edge))) 15 | 16 | (area (->Square 5)) 17 | -------------------------------------------------------------------------------- /exercises/tic-tac-toe.clj: -------------------------------------------------------------------------------- 1 | (def board [:x :e :o 2 | :x :e :e 3 | :x :e :o]) 4 | 5 | (let [[a1 b1 c1 6 | a2 b2 c2 7 | a3 b3 c3] board 8 | 9 | solutions (hash-set 10 | [a1 a2 a3] 11 | [b1 b2 b3] 12 | [c1 c2 c3] 13 | [a1 b1 c1] 14 | [a2 b2 c2] 15 | [a3 b3 c3] 16 | [a1 b2 c3] 17 | [a3 b2 c1])] 18 | (cond 19 | (contains? solutions [:x :x :x]) 20 | :x 21 | 22 | (contains? solutions [:o :o :o]) 23 | :o)) 24 | 25 | -------------------------------------------------------------------------------- /exercises/todo.clj: -------------------------------------------------------------------------------- 1 | 2 | (loop [todos []] 3 | (let [todo (read-line)] 4 | (if (not= todo "q") 5 | (recur (conj todos todo)) 6 | todos))) 7 | 8 | -------------------------------------------------------------------------------- /src/example/core_server.clj: -------------------------------------------------------------------------------- 1 | (ns example.core-server 2 | (:require [org.httpkit.server :as server] 3 | [hiccup.core :as hiccup] 4 | [clojure.string :as str])) 5 | 6 | (defn get-people [] 7 | (let [people (slurp "docs/people.csv") 8 | people (str/split-lines people) 9 | header (first people) 10 | people (rest people) 11 | header (str/split header #",") 12 | header (map keyword header) 13 | people (map 14 | (fn [person] 15 | (zipmap header (str/split person #","))) 16 | people)] 17 | people)) 18 | 19 | (defmulti handler :uri) 20 | 21 | (defmethod handler "/" [request] 22 | {:status 200 23 | :body (hiccup/html 24 | [:html 25 | [:body 26 | [:ol 27 | (map 28 | (fn [person] 29 | [:li (:first_name person)]) 30 | (get-people))]]])}) 31 | 32 | (defmethod handler "/philippines" [request] 33 | {:status 200 34 | :body (hiccup/html 35 | [:html 36 | [:body 37 | [:ol 38 | (->> (get-people) 39 | (filter 40 | (fn [person] 41 | (= (:country person) 42 | "Philippines"))) 43 | (map 44 | (fn [person] 45 | [:li (:first_name person)])))]]])}) 46 | 47 | 48 | (defn -main [] 49 | (server/run-server (var handler) {:port 3000}) 50 | (println "Started server")) 51 | 52 | --------------------------------------------------------------------------------(keep cljs.reader/read-string (read-tokens text)) 33 |