The purpose of this guide is to take you on a fascinating journey using a little Clojure. The journey will begin with the very simplest of programs and end up with the building of graphical user interfaces and web sites.
The strategy employed is absurd simplicity. You will see very little text and mostly just Clojure code presented in such a way that each concept leads to the next in a compelling logical story. Don’t be daunted. Just read the code, and examine the output produced by that code, and then reason out what that code is doing. Then play! Change the code and see what happens. You’ll learn faster than you thought possible.
To get started we’re going to need to be able to type commands into a REPL (Read Evaluate Print Loop). You can get to a REPL using any computer connected to the internet by going to https://clojure.org/guides/install_clojure and following the instructions there.
Once you have clojure installed, you should be able to type clj into a command window. You should see something more or less like this:
Clojure 1.11.1
user=>
In the examples that follow, I will often replace user=> with just => just to keep things simple at first.
Each chapter ends with exercises. I urge you, very strongly, to work through these exercises. The answers (or at least my answers) are in the appendix. If you already know a bit of Clojure then try to solve the exercises with only the features of the language that have been discussed up to that point. Some of they will be quite challenging.
And now we are ready to begin…and remember to play!
At the REPL type (+ 1 1) and hit enter or return. Your screen should look like this now.
=> (+ 1 1)
2
Continue to try each of the following examples.
=> (+ 1 1 1)
3
=> (+ 1 1 1 1 1 1 1 1 1)
9
=> (+ 1)
1
=> (+)
0
The sum of nothing is zero — the additive identity.
Does this work with subtraction?
=> (-)
Execution error (ArityException) at user/eval3 (REPL:1).
Wrong number of args (0) passed to: clojure.core/-
Nope.
=> (- 1)
-1
=> (- 1 1)
0
=> (- 1 1 1)
-1
Multiplication works as you might expect.
=> (*)
1
=> (* 1)
1
=>(* 2 3)
6
=>(* 2 3 4)
24
Division is a bit different.
=> (/ 1)
1
=> (/ 2)
1/2
Aha! That 1/2 is a rational number. In order to be exact clojure will avoid creating decimal expansions if it can, as you can see by the following examples.
=> (/ 1 3)
1/3
=> (/ 2 3 4)
1/6
Clojure always reduces rational numbers to lowest terms.
If you’d rather see decimal expansions then you can use floating point[1] numbers as follows.
=> (/ 1.0 2.0)
0.5
=> (/ 1.0 7.0)
0.14285714285714285
That last value is approximate. That’s the problem with floating point numbers, they are seldom exact. Whereas integers and rational numbers are always exact.
=> (type 1)
java.lang.Long
=> (type 1/2)
clojure.lang.Ratio
=> (type 1.0)
java.lang.Double
Ignore the java.lang. Long is a synonym for integer. Ratio is a synonym for rational. Double is a synonym for floating point.
Sometimes we want to group two or more numbers together. We can do that using a vector.
=> [1 2]
[1 2]
=> [1 2 3]
[1 2 3]
=> [1]
[1]
=> []
[]
=> (first [1 2 3])
1
=> (rest [1 2 3])
(2 3)
=> (last [1 2 3])
3
=> (second [1 2 3])
2
=> (count [1 1 1 1 1 1 1])
7
=> (nth [9 8 7 6 5] 3)
6
The nth operation presumes that the items are numbered from zero.
=> (type [1])
clojure.lang.PersistentVector
Another data type, similar to vectors, is the list.
=> '(1 2 3)
(1 2 3)
=> '(1)
(1)
=> '()
()
=> (first '(1 2 3))
1
=> (rest '(1 2 3))
(2 3)
=> (second '(1 2 3))
2
=> (last '(1 2 3))
3
=> (count '(1 1 1 1 1 1 1))
7
=> (nth '(1 2 3) 1)
2
=> (type '(1))
clojure.lang.PersistentList
The difference[2] between lists and vectors is the way they are stored inside the computer. Vectors are designed to grow from the back, whereas lists are designed to grow from the front. We often use the conj operation to grow a list or a vector.
=> (conj [1 2 3] 4)
[1 2 3 4]
=> (conj '(1 2 3) 4)
(4 1 2 3)
We can also concatenate lists and vectors together.
=> (concat [1 2 3] [4 5 6])
(1 2 3 4 5 6)
=> (concat [1 2 3] '(4 5 6))
(1 2 3 4 5 6)
So far we’ve been using the word “operation” to refer to +, -, *, /, type, first, second, last, and nth. We’ve also been using the word “parameter” to describe the data being operated upon. From now on we are going to use the word “function” and “argument” respectively, for reasons that will become clear soon.
The inc and dec functions simply increment (add one to) and decrement (subtract one from) their arguments.
=> (inc 1)
2
=> (dec 1)
0
=> (inc 1/2)
3/2
=> (dec 4/5)
-1/5
=> (inc 4.0)
5.0
=> (dec 6.2)
5.2
The map function applies another function across the entirely of a list or vector.
=> (map inc '(1 2 3))
(2 3 4)
=> (map dec [5 6 7])
(4 5 6)
Notice that in the last example, even though the argument was a vector, the returned value was a list[3], not a vector. This is typical of many functions in the clojure library — and there’s a good reason for it that you’ll eventually learn. It’s not important right now.
=> (map + [1 2 3 4] [5 6 7 8])
(6 8 10 12)
=> (map * [1 2 3] [4 5 6] [-1 0 1])
(-4 0 18)
Let’s make a program we can call.
=> (defn square [x] (* x x))
#'user/square
=> (square 2)
4
=> (square 1/2)
1/4
=> (square 0.3)
0.09
=> (square (square (square 2)))
256
It looks like defn is a function that creates another function. That’s not quite what defn is, but we’ll go with that for the time being. The first argument to defn is the name of the function being created (square). The next argument ([x]) is a vector naming the argument(s) of the function. The last argument ((* x x)) is a list that contains the executable body of the function.
Now let’s look at some standard comparisons.
=> (pos? 1)
true
=> (pos? -1)
false
=> (pos? 0)
false
=> (neg? 1)
false
=> (neg? -1)
true
=> (neg? 0)
false
=> (zero? 1)
false
=> (zero? -1)
false
=> (zero? 0)
true
=> (< 1 2)
true
=> (< 2 1)
false
=> (> 1 2)
false
=> (> 2 1)
true
The above are just simply magnitude comparators, but you can add more arguments…
=> (< 1 2 3)
true
=> (> 3 2 1)
true
So 1<2<3 and 3>2>1. This is really useful for those validations between limits. And, of course the corresponding >= and <= functions work the same way.
=> (<= 1 1 2)
true
=> (>= 5 4 4)
true
With single argument comparators like pos? and neg? you can filter items from lists.
=> (filter neg? [3 5 -2 7 -1])
(-2 -1)
=> (remove pos? [-7 3 -2 1])
(-7 -2)
Sometimes you’ll have a list that you want to be the arguments of a function. That’s what the apply function is for.
=> (apply * [2 3 4])
24
=> (apply + '(7 6 5))
18
So far we’ve been dealing with numbers. But, of course, Clojure can also deal with strings.
=> (type "string")
java.lang.String
=> "string"
"string"
The str function coerces its arguments into strings and concatenates them.
=> (str "concatenate" " " "strings")
"concatenate strings"
=> (str 1 2 3)
"123"
The char function coerces its argument into a character. Most data types have corresponding coercion functions.
(type \A)
java.lang.Character
=> (char 65)
\A
=> (int \A)
65
=> (boolean 1)
true
=> (boolean 0)
true
=> (boolean nil)
false
=> (double 1)
1.0
=> (double 1/2)
0.5
=> (rationalize 0.5)
1/2
=> (int 1.5)
1
=> (int 1/2)
0
=> (int 3/2)
1
Strings behave like lists.
=> (first "string")
\s
=> (rest "string")
(\t \r \i \n \g)
=> (last "string")
\g
=> (nth "string" 3)
\i
The def statement below creates a new variable named string-chars containing the list of characters in “string”.
=> (def string-chars (map char "string"))
#'user/string-chars
=> string-chars
(\s \t \r \i \n \g)
=> (apply str string-chars)
"string"
Now let’s apply what we’ve learned to solve a few simple problems. The formula for converting Celsius temperatures to Fahrenheit is (9/5c + 32).
=> (defn ctof [c] (+ 32.0 (* 9/5 c)))
=> (ctof -40)
-40.0
=> (ctof 0)
32.0
=> (ctof 100)
212.0
The complimentary formula that converts Fahrenheit to Celsius is (5/9(f-32)).
=> (defn ftoc [f] (* 5/9 (- f 32.0)))
=> (ftoc 32)
0.0
=> (ftoc 212)
100.0
=> (ftoc -40)
-40.0
1. Albert.
Create a function to calculate the energy content of a given mass using Einstein’s formula E=mc2. The constant c is 299,792,458 meters per second, m will be in kilograms, and E will be in Joules.
2. Mix it up.
Write a function named mix that takes two strings and mixes them into a single string. Thus (mix “abcde” “12345”) returns “a1b2c3d4e5”.
3. Average.
Write a function named mean that computes the average of a list of numbers. Thus (mean [1 2]) should return 3/2. Now change it so that it returns 1.5.
4. Power.
The (repeat n x) function produces a list of the x element repeated n times. Thus (repeat 3 “bob”) returns (“bob” “bob” “bob”). Use this to create a function (pow n e) that computes ne where e is a non-negative integer. Make sure you test the case where e is zero.
5. Filter.
Write a function named pos-neg which takes a list of numbers and returns a list containing all the positives followed by any zeros, and then by all the negatives. Thus (pos-neg [6 -1 0 4 -2 0]) should return (6 4 0 0 -1 -2).
6. Pythagorus.
Write a function named pythag? that takes three arguments a, b, and c, which are the lengths of the sides of a triangle. Return true if the triangle has a 90° angle — or, in other words, if the three arguments form a Pythagorean triplet: c2=a2+b2. Thus, (pythag? 3 4 5) should return true, whereas (pythag? 1 2 3) should return false.
It is often useful to know if a list is empty.
=> (empty? [])
true
=> (empty? '())
true
=> (empty? "")
true
=> (empty? nil)
true
=> (map empty? [[1] '(1) "x"])
(false false false)
=> (doc empty?)
-------------------------
clojure.core/empty?
([coll])
Returns true if coll has no items - same as (not (seq coll)).
Please use the idiom (seq x) rather than (not (empty? x))
nil
It’s also useful to know if something is nil or not.
=> (nil? nil)
true
=> (nil? 1)
false
=> (nil? [])
false
=> (some? nil)
false
=> (some? 1)
true
=> (some? [])
true
=> (doc nil?)
-------------------------
clojure.core/nil?
([x])
Returns true if x is nil, false otherwise.
nil
=> (doc some?)
-------------------------
clojure.core/some?
([x])
Returns true if x is not nil, false otherwise.
nil
We’ll have more to say about the doc function later. For now, notice that the function returns nil.
=> (and true false)
false
=> (and true true)
true
=> (and true true false)
false
=> (and true true true)
true
=> (or true false)
true
=> (or false false)
false
=> (or false true false)
true
Now let’s make some decisions.
=> (if true 1 2)
1
=> (if true 2 1)
2
=> (if true 1)
1
=> (if false 1 2)
2
=> (if false 2 1)
1
=> (if false 1)
nil
Take note of that last one, it can trip you up unless you are aware of it. Notice also that if is a function that returns the selected value.
=> (when true 1)
1
=> (when false 1)
nil
Does when look like if? Here’s where it differs.
=> (when true 1 2 3)
3
=> (when true (print "hello, ") (print "world."))
hello, world.nil
The when function executes every statement in the list if the predicate is true. The value returned is the value of the last statement executed. The when function is a lot like an if with a do, which gathers several statements into one.
=> (if true (do (print "hi ") (println "there.")))
hi there.
nil
Statements like these read better when they are indented.
=> (if true
(do
(print "hi ")
(println "there.")))
hi there.
nil
Now let’s test for equality.
=> (= 1 1)
true
=> (= 2 2)
true
=> (= 2 1)
false
=> (= 1/2 1/2)
true
=> (= 1/2 2/4)
true
=> (= 1/3 (/ 1 3))
true
=> (not= 1 1)
false
=> (not= 1 2)
true
So far, so good. But take care with numbers of different types.
=> (= 1 1.0)
false
=> (= 1/2 (/ 1 2.0))
false
And always remember that comparing floating point number for equality is risky, because floating point numbers have a limited number of digits to work with.
=> (= 1.0 1.0)
true
=> (def x (/ 1.0 3.0))
=> (def y (* x x x x x x))
=> (def z (* y 3 3 3 3 3 3))
=> (= 1.0 z)
false
=> z
0.9999999999999996
Of course numbers aren’t the only things that can be compared for equality.
=> (= "string" "string")
true
=> (= "string" "strung")
false
=> (= [] [])
true
=> (= [1 2 3] [1 2 3])
true
=> (= [1 2 3] [4 5 6])
false
=> (= [1 2 3] '(1 2 3))
true
Sometimes we need to select more than one or two options. For that we can use the cond function.
=> (cond true "hi" false "there")
"hi"
=> (cond false "hi" true "there")
"there"
=> (cond false "hi" false "there" true "Bob")
"Bob"
=> (cond false "hi" false "there" false "Bob")
nil
The first true wins.
=> (cond true "hi" false "there" true "Bob")
"hi"
Don’t worry about the :else below, that’s just a synonym for true in this context.
=> (cond false "hi" false "there" false "Bob" :else "else")
"else"
And, as always, a little indenting really helps the reader.
=> (cond
false "hi"
false "there"
false "Bob"
:else "else")
"else"
Now let’s put a few things together.
=> (defn check [x]
(cond
(= 0 x) "zero"
(= 1 x) "one"
(= 2 x) "two"
:else "many"))
=> (check 0)
"zero"
=> (check 1)
"one"
=> (check 2)
"two"
=> (check 3)
"many"
Those predicates can get pretty redundant, so we can use condp to make things a bit tidier.
=> (defn check2 [x]
(condp = x
0 "zero"
1 "one"
2 "two"
"many"))
=> (check2 0)
"zero"
=> (check2 1)
"one"
=> (check2 2)
"two"
=> (check2 3)
"many"
The first argument to condp is a function that takes two arguments and returns a boolean value. In this case the function was =. The condp function calls that function with its second argument and then with the first element of each row in turn. The first row to match causes the second element of that row to be returned.
Now let’s loop through a list. One of the easiest ways to do that is with the for function.
=> (for [x [1 2 3]] x)
(1 2 3)
The first argument of the for function is a vector of bindings. A binding is itself a vector that relates a variable to a value — or in this case a sequence of values. The second argument of the for function is an expression that uses those bindings. In this case that expression is simply x. But, of course, it can be more interesting than that.
=> (for [x [1 2 3]] (inc x))
(2 3 4)
In this case the expression is (inc x). This may look a lot like (map inc [1 2 3]), but the next example will break that similarity.
=> (for [x [1 2 3] y [4 5 6]] [x y])
([1 4] [1 5] [1 6] [2 4] [2 5] [2 6] [3 4] [3 5] [3 6])
When the for function is given more than one binding it loops through every possible combination. The expression above simply puts the two bound values into a vector.
A common mistake that Clojure programmers make is to use the for function with an expression that doesn’t return a sensible value. For example:
=> (for [x '(3 4 5)] (prn x))
(3
4
nil 5
nil nil)
We’ll learn why this appears so shuffled up in a later chapter; but the gist of the issue is that the prn function returns nil. Thus, the for function returned (nil nil nil), and when the REPL tried to print it got shuffled with the action of prn printing the values.
When you want to loop through a list, but you don’t care to gather the values you can use doseq instead; and it will still loop through all the values but will simply return nil.
=> (doseq [x '(3 4 5)] (prn x))
3
4
5
nil
1. Quad.
The function (Math/sqrt) returns the square root of its argument. Write a function named quad to solve for x in the quadratic equation 0=ax2+bx+c. Use the quadratic formula (-b±sqrt(b2-4ac))/2a. The results should look like this:
=> (quad 1 2 1)
-1
=> (quad 1 2 3)
"imaginary"
=> (quad 0 2 3)
-3/2
=> (quad 1 1 0)
[0.0 -1.0]
=> (quad 1 -0.25 -0.125)
[0.5 -0.25]
2. Fizzbuzz.
The function (rem n d) returns the remainder of n divided by d. The function (range a b) returns a list of integers from a to b-1. Write a function named fizzbuzz that takes a single argument. The function returns a list of all the integers from 1 up to and including that argument, replacing every integer divisible by 3 with “fizz”, every integer divisible by 5 with “buzz”, and every integer divisible by both 3 and 5 with “fizzbuzz”. The results, up to 20, should look like this:
=> (fizzbuzz 20)
(1 2 "fizz" 4 "buzz" "fizz" 7 8 "fizz" "buzz" 11
"fizz" 13 14 "fizzbuzz" 16 17 "fizz" 19 "buzz")
3. Distances.
First, study the output of (doc for). Then given the following data structure which defines a set of named points on the x-y plane:
(def points [["O" 0 0] ["A" 1 1] ["B" 1 2] ["C" 2 3] ["D" 4 5]])
Remembering that the formula for the distance between two points is:
sqrt((x1-x2)2+(y1-y2)2).
Write the function distances, and any other functions that you think will support it. When you call distances with the points data structure above it should print one line for every pair of points showing the distance between them. The output should look like this:
=> (distances points)
O-A 1.4142135623730951
O-B 2.23606797749979
O-C 3.605551275463989
O-D 6.4031242374328485
A-O 1.4142135623730951
A-B 1.0
A-C 2.23606797749979
A-D 5.0
B-O 2.23606797749979
B-A 1.0
B-C 1.4142135623730951
B-D 4.242640687119285
C-O 3.605551275463989
C-A 2.23606797749979
C-B 1.4142135623730951
C-D 2.8284271247461903
D-O 6.4031242374328485
D-A 5.0
D-B 4.242640687119285
D-C 2.8284271247461903
nil
Our programs are going to get a bit longer now, so you might want to type them into an editor and then pasted them into the REPL.
Let’s start by computing factorials. The factorial of the integer x is denoted by x! and is the product of all the integers from 1 to x. Thus we could write the fac function as follows:
(defn fac [n] (apply * (range 1 (inc n))))
However, as you’ll soon see, this doesn’t suit our purposes for this chapter. Instead let’s write it like this:
(defn fac
([n] (fac n 1))
([n f] (if (= n 1) f (fac (dec n) (* n f)))))
This defines two functions. The first (fac n) returns n!. The second (fac n f) is a worker function that does the actual computation. When two functions have the same name but have a different number of arguments we say that the two functions are overloaded.
Notice that the worker function calls itself. When a function calls itself like that it is known as recursion.
Now if you follow the logic through you’ll see that (fac n) calls (fac n 1) which calls itself over and over, decrementing n each time until (= n 1) . Some results are:
=> (fac 1)
1
=> (fac 2)
2
=> (fac 3)
6
=> (fac 10)
3628800
=> (fac 20)
2432902008176640000
=> (fac 21)
Execution error (ArithmeticException) at java.lang.Math/multiplyExact (Math.java:1032).
long overflow
Why can’t we go past 20!? Because in order for computers to go fast, they limit the size of the numbers they usually manipulate. But we can direct the computer to use bigger numbers so long as we don’t mind the computer going a little bit slower[4]. All we need to do is replace the 1 with 1N.
(defn fac
([n] (fac n 1N))
([n f] (if (= n 1) f (fac (dec n) (* n f)))))
Now we can calculate very large factorials indeed!
=> (fac 21)
51090942171709440000N
=> (fac 50)
30414093201713378043612608166064768844377641568960512000000000000N
=> (fac 100)
9332621544394415268169923885626670049071596826438162146859296389521759999322
9915608941463976156518286253697920827223758251185210916864000000000000000000000000N
The difference between 1N and 1 is just the type of the integer:
=> (type 1)
java.lang.Long
=> (type 1N)
clojure.lang.BigInt
Long integers are big enough for most purposes, and when the computer uses them it can go fast. BigInt integers are as long as you need them to be. They can be thousands[5] of digits long. But the computers slows down considerably when it uses them. Let’s see this in action by using the time function.
=> (time (apply + (repeat 1000000 1N)))
"Elapsed time: 105.699372 msecs"
1000000N
=> (time (apply + (repeat 1000000 1)))
"Elapsed time: 71.702522 msecs"
1000000
Using BigInt cost us 34 milliseconds for a million additions. So we’re not going to worry about it too much here. And, anyway, we’re going to need those big numbers for what we are going to do next.
=> (/ (fac 20) (fac 30))
1/109027350432000
Rational numbers have no limits on the number of digits they use. This can be very useful for preserving the precision of your calculations.
There is another way to preserve the precision of calculations; but it’s a bit trickier. You can use the BigDecimal type.
=> (type 1M)
java.math.BigDecimal
=> (/ 1M 2)
0.5M
=> (+ 1M 1e-50M)
1.00000000000000000000000000000000000000000000000001M
=> (/ 1M 3)
Execution error (ArithmeticException) at java.math.BigDecimal/divide (BigDecimal.java:1783).
Non-terminating decimal expansion; no exact representable decimal result.
There is no practical limit on the number of decimal places a BigDec can hold. However, it will refuse to hold a repeating decimal because they have an infinite number of digits. So you can use BigDec only when you know that your decimals will terminate, or…you can cheat.
=> (with-precision 20 (/ 1M 3))
0.33333333333333333333M
When you cheat like this, you lose precision. So we’re not going to be doing that in this chapter. We want precision! So we’ll use BigInt and Ratio types.
So now let’s compute powers of numbers with high precision.
(defn pow
([n x] (pow n x 1N))
([n x p] (if (< x 1) p (pow n (dec x) (* p n)))))
Once again we are using an overloaded recursive function. The result is:
=> (pow 2 5)
32N
=> (pow 2 100)
1267650600228229401496703205376N
=> (pow 10 30)
1000000000000000000000000000000N
We can use this function with BigDec to preserve precision of decimals taken to powers.
=> (pow 1.6 10)
109.95116277760006
=> (pow 1.6M 10)
109.9511627776M
=> (pow 1.6 20)
12089.258196146306
=> (pow 1.6M 20)
12089.25819614629174706176M
Notice that when we use Double types we lose precision; but when we use BigDec types we keep the precision. Again, Double types are good enough for most purposes, and are faster than BigDec types; but sometimes precision is more important than speed.
So, what can we do with powers and factorials? We can use a Taylor[6] series to approximate the values of transcendental functions. Let’s create a function to calculate the trigonometric sine of an angle.
The Taylor series for sin(x) is x-x3/3!+x5/5!-x7/7!… where x is in radians.
So first, let’s create a function that maps the exponent to 1 or -1 depending on the sign of the term.
(defn sin-term-sign [n] (- (- (rem n 4) 2)))
=> (range 1 10 2)
(1 3 5 7 9)
=> (map sin-term-sign (range 1 10 2))
(1 -1 1 -1 1)
Now let’s write a function that calculates the value of the nth term.
(defn sin-term [x n]
(* (sin-term-sign n)
(/ (pow x n) (fac n))))
=> (sin-term 1 1)
1N
=> (sin-term 1 3)
-1/6
=> (sin-term 1 5)
1/120
Nice rational numbers.
(defn sin [x n]
(let [ns (range 1 n 2)
terms (map (fn [n] (sin-term x n)) ns)]
(apply + terms)))
The let function takes a vector of bindings that sets the values of the named variables. The fn function is similar to defn except that it returns an anonymous function. Thus ns is a list of the exponents up to n, and terms is a list of all the terms of the Taylor series up to n.
=> (sin 1 5)
5/6
=> (sin 1 10)
305353/362880
=> (sin 1 20)
102360822438075317/121645100408832000
It should now be clear how nice rational numbers are for preserving precision. On the other hand, they aren’t particularly useful in human terms. I mean, nobody really wants to know that the sine of 1 radian is 102360822438075317÷121645100408832000. But we can fix that by converting the rational number to a BigDecimal.
=> (with-precision 20 (bigdec (sin 1 20)))
0.84147098480789650663M
That’s a pretty good estimate of sin(1). It’s better than the calculator on my iPhone. It’s also a little better than the standard Math/sin function.
=> (Math/sin 1)
0.8414709848078965
So let’s try something other than 1. The sine of π is zero. The standard function, and our function are both pretty doggone close to zero. And our function computes sin(π/2) = 1 to within very tight tolerance.
=> (Math/sin Math/PI)
1.2246467991473532E-16
=> (sin Math/PI 30)
-1.1097266936275162E-16
=> (sin (/ Math/PI 2) 30)
1.0000000000000002
However, as you can see below, our function does have some limitations It’s really only accurate in the range of -π to π.
=> (sin 100 30)
17627235963358095945458935270121351192759970700/16864322650412944707
=> (with-precision 20 (bigdec (sin 100 30)))
1.0452383015173437945E+27M
1. Arctangent.
Write the arctan function. The Taylor formula is x-x3/3+x5/5-x7/7… It turns out that if you multiply arctan(1) by 4 it equals π. How close to π can you get? How many terms does it take to get to 3.14…
2. Generate π.
You may have found that last exercise a bit frustrating. Perhaps you encountered a stack overflow error, or perhaps it just ran for too long. So let’s fix that. Let’s generate π by multiplying arctan(1) by 4, but this time let’s be smart about it. We do not need to raise 1 to any power. And we do not need to use rational numbers because they can be very slow. So see if you can change your solution to quickly generate π to five decimal places. 3.14159… Can you go farther?
3. Seriously π.
OK, let’s stop pussyfooting around here, Batman. Let’s generate π to 1,000 decimals. That would take forever with our last algorithm, so we’ll need something better. The problem is that 1 in the numerator. Those terms simply do not converge very quickly. So we need to get numerators that are a lot smaller than 1, so that their powers are much much smaller than 1. It turns out that:
π/4 = 12arctan(1/38)+20arctan(1/57)+7arctan(1/239)+24arctan(1/268).
Those arctans should converge pretty rapidly. So, put the arctan function back the way it was for exercise 1. Yes, the rational numbers are a bit slow; but with this algorithm it shouldn’t matter. Now write the function pi-gen that takes a single argument which is the number of digits to calculate. You should quickly get a result like this:
=> (pi-gen 100)
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068M
Now try (pi-gen 1000). Can you see the string of six nines about three quarters[7] of the way through?
Try to figure out how many decimals of π you can calculate in one hour of computer time.
In the previous chapter we introduced the concept of anonymous functions. For example, the following is an anonymous function that returns the square of its argument.
(fn [x] (* x x))
The fn returns that function as a value. This means we can assign that value to a variable as follows:
(def square (fn [x] (* x x)))
=> (square 2)
4
Look closely, that’s a def not a defn. In fact, the statement above is pretty much what defn does.
At this point I need to confess that I have been deliberately misleading you about something. I have been stating that the f in a structure like (f x) is a function. That’s not always the case. Sometimes that f might be a macro, like defn. Sometimes it might be a special form, like fn. It’s too soon to go into the details; but I thought it wise come clean about this before you run into it unprepared.
Special forms, like fn, are actually part of the Clojure language. The job of fn is to create and return an anonymous function.
Macros are functions that translate their arguments into Clojure syntax. The macro, defn, is a function that translates its arguments into a Clojure statement using the def and fn special forms to create a named function.
So the defn macro translates (defn square [x] (* x x)) into (def square (fn [x] (* x x))).
When would you use an anonymous function? Often we use them as arguments to functions like map and filter.
=> (map (fn [x] (* x x x)) [1 2 3])
(1 8 27)
Some folks find that to be a little wordy, or inconvenient. So there is another way to specify an anonymous function:
=> (map #(* % % %) [1 2 3])
(1 8 27)
The #() syntax is known as the dispatch reader macro. Reader macros are conveniences implemented in the part of Clojure that reads in the program. The dispatch reader macro converts the #(* % % %) syntax into (fn [x] (* x x x)). Don’t worry overmuch about special forms, macros, and reader macros. We’ll dig more deeply into them later.
In the mean time, let’s compare the three ways to define a typical function:
=> (def midway (fn [a b] (/ (+ a b) 2)))
=> (midway 6 9)
15/2
=> (defn midway [a b] (/ (+ a b) 2))
=> (midway 6 12)
9
=> (def midway #(/ (+ %1 %2) 2))
=> (midway 3 7)
5
The first is pure Clojure since it doesn’t rely on any macros or reader macros. The second is the most common, it’s what most programmers expect to see. The third is the most concise, but most programmers would wrinkle up their nose if they saw it done that way.
Speaking of multiple ways to do things, consider this:
=> (apply + [1 2 3])
6
=> (reduce + [1 2 3])
6
These two functions appear to do the same thing — but they do it in two very different ways. The above apply is equivalent to (+ 1 2 3), but the reduce is equivalent to (+ (3 (+ 1 2)). The first argument of reduce is a function (f) that takes two arguments. The second argument is a list. The reduce function applies the first two elements of the list to f, and then applies the result and the next element of the list to f again. This continues until the list is exhausted.
So (reduce + [1 2 3 4 5]) is equivalent to (+ 5 (+ 4 (+ 3 (+ 1 2)))).
Now consider using midway and reduce together:
=> (reduce midway [2 3])
5/2
No surprise there. 5/2 is midway between 2 and 3.
=> (reduce midway [2 3 4])
13/4
Perhaps that one surprised you, but consider (midway 4 (midway 2 3)) is the midpoint between 5/2 and 4, and that equals 13/4. The next two should not surprise you.
=> (reduce midway [2 3 4 5])
33/8
=> (reduce midway [0 2 3 4 5])
4
=> (reduce midway 0 [2 3 4 5])
4
The last two are equivalent. The extra argument is a convenience in case you want to prepend a starting value to the list as an initial value.
Now consider this nifty function which returns all the intermediate values of reduce.
=> (reductions midway [1 2 3 4 5])
(1 3/2 9/4 25/8 65/16)
You might be wondering when something like that would ever be useful. My only answer to that is — you’ll be surprised.
Now let’s look at another convenient macro — the threading macro. Let’s say we want to find the reduced midway between the squares of the first ten integers.
=> (reduce midway (map square (range 10)))
33789/512
Does that seem inside-out to you? If so, try this:
=> (->> (range 10) (map square) (reduce midway))
33789/512
The threading macro simply translates (->> (range 10) (map square) (reduce midway)) into (reduce midway (map square (range 10))). Some people find the threaded syntax to be more intuitive since time seems to move from left to right.
For example, the above could be read in chronological order as:
This chronological intuition sometimes becomes more pronounced the longer the chain of functions is
=> (->> (range 10) (map square) (reductions midway))
(0 1/2 9/4 45/8 173/16 573/32 1725/64 4861/128 13053/256 33789/512)
=> (->> (range 10) (map square) (reductions midway) (map double))
(0.0 0.5 2.25 5.625 10.8125 17.90625 26.953125 37.9765625 50.98828125 65.994140625)
There are two threading macros. The ->> macro passes each result into the last argument of the next function in the thread. Thus (->> a (f 1)) is translated into (f 1 a). The -> macro passes each result into the first argument of the next function in the thread. Thus (-> a (f 1)) is translated into (f a 1).
Here’s a use of threading, reduce, and anonymous functions for you to ponder. It calculates the standard deviation of a list of numbers.
(defn mean [l] (/ (reduce + l) (count l)))
(defn sigma [l] (let [u (mean l)] (->> l (map #(- % u)) (map square) mean Math/sqrt)))
=> (sigma [1 2 3 4 5])
1.4142135623730951
If you found that sigma function a bit hard to read, consider this version:
(defn sigma [l]
(let [u (mean l)]
(->> l
(map #(- % u))
(map square)
mean
Math/sqrt)))
Sometimes a little bit of formatting can work wonders.
1. Unthread.
Rewrite the sigma function without threading and without anonymous functions.
2. Popularity.
The popularity of a movie is calculated as the sum of the number of people who attend it each day plus half the previous day’s popularity. What is the popularity of
[100 150 90 200 120 150 90]
3. Ships.
There is a ship on a very flat ocean. It’s position is represented by [x y]. The ship starts out motionless at [0 0]. There is a vector of thrusts, one for each “tick” of time. Each thrust is a vector of [fx fy] that describes the increase of velocity per tick in the x and y directions created by the thrust. What is the position and velocity of the ship after the thrust vector of [[1 1] [0 0] [2 0] [-1 2] [-1 -1]] has been applied
(def m {"one" 1 "two" 2})
=> m
{"one" 1, "two" 2}
=> (type m)
clojure.lang.PersistentArrayMap
=> (get m "one")
1
=> (get m "two")
2
=> (get m "three")
nil
=> (get m "three" "none")
"none"
I think that’s pretty self-descriptive. The {key value} syntax describes a key-value map. You can use get to get the value by using the associated key. And you can supply a default value for the case when no such key exists.
The following syntax may be a bit more surprising.
=> (m "one")
1
=> (m "two")
2
=> (m "three")
nil
=> (m "three" "none")
"none"
This is one of many shorthand conveniences that Clojure offers. Even though m is not a function, Clojure considers a map to be a function that operates like get.
There are many functions that will inspect a map.
=> (keys m)
("one" "two")
=> (vals m)
(1 2)
=> (contains? m "one")
true
=> (contains? m "two")
true
=> (contains? m 1)
false
=> (contains? m 2)
false
=> (contains? m "three")
false
=> (find m "one")
["one" 1]
That last one is a bit interesting. It looks like it returns a vector. But it actually returns a MapEntry.
=> (type (find m "one"))
clojure.lang.MapEntry
=> (find m "two")
["two" 2]
=> (find m "three")
nil
=> (first m)
["one" 1]
=> (type (first m))
clojure.lang.MapEntry
=> (second m)
["two" 2]
=> (type (second m))
clojure.lang.MapEntry
=> (key (first m))
"one"
=> (val (first m))
1
From this we can infer that a map is just a sequence of MapEntrys which can be queried with the key and val functions. Again, MapEntrys look like vectors.
=> (def me (first m))
=> me
["one" 1]
=> (key me)
"one"
=> (val me)
1
=> (first me)
"one"
=> (second me)
1
MapEntrys seem to behave like vectors too. But key and val don’t work on vectors.
=> (def not-me ["one" 1])
=> (first not-me)
"one"
=> (second not-me)
1
=> (key not-me)
Execution error (ClassCastException) at user/eval612 (REPL:1).
class clojure.lang.PersistentVector cannot be cast to class java.util.Map$Entry
=> (val not-me)
Execution error (ClassCastException) at user/eval614 (REPL:1).
class clojure.lang.PersistentVector cannot be cast to class java.util.Map$Entry
I guess that’s just a bit of type safety of something…
Anyway since maps are sequences…
=> (map #(str (first %) ":" (second %)) m)
("one:1" "two:2")
=> (->> m (map #(str (first %) ":" (second %))))
("one:1" "two:2")
Notice that it is the MapEntry getting fed into the % of the anonymous function.
OK, now let’s modify the map.
=> (assoc m "three" 3)
{"one" 1, "two" 2, "three" 3}
=> m
{"one" 1, "two" 2}
=> (def m2 (assoc m "three" 3))
=> m2
{"one" 1, "two" 2, "three" 3}
=> m
{"one" 1, "two" 2}
=> (dissoc m2 "three")
{"one" 1, "two" 2}
=> m2
{"one" 1, "two" 2, "three" 3}
Welcome to functional programming. You cannot modify the map. Indeed, you cannot modify any variable[8]. However, you can create new variables from old ones.
=> (update m "one" inc)
{"one" 2, "two" 2}
The update function uses the passed in function to modify the value. This is more convenient than the more obtuse:
=> (assoc m "one" (inc (m "one")))
{"one" 2, "two" 2}
=> :this
:this
=> (type :this)
clojure.lang.Keyword
=> (name :this)
"this"
=> (keyword "this")
:this
Keywords are named tokens. They are comparable…
=> (= :this :that)
false
=> (= :this :this)
true
…but not numeric
=> (<= :this :this)
Execution error (ClassCastException) at user/eval173 (REPL:1).
class clojure.lang.Keyword cannot be cast to class java.lang.Number
They have no value other than their name.
=> (str :this)
":this"
=> (prn :this)
:this
nil
=> (println :this)
:this
nil
And they are very, very, useful — especially when combined with maps.
=> (def mk {:one 1 :two 2})
=> (mk :one)
1
=> (mk :two)
2
=> (:one mk)
1
=> (:two mk)
2
=> (:three mk)
nil
=> (:three mk :none)
:none
That’s another convenience. Keywords are implicit invocations of get. In fact, you can pass them as functions like this:
=> (map :one [{:one 1} {:one "one"}])
(1 "one")
But now let’s get down to business…
=> (def bob {:bob {:first "Bobby" :last "Martin"}})
=> (def bill {:bill {:first "Billy" :last "Smith"}})
=> bob
{:bob {:first "Bobby", :last "Martin"}}
=> bill
{:bill {:first "Billy", :last "Smith"}}
=> (merge bob bill)
{:bob {:first "Bobby", :last "Martin"}, :bill {:first "Billy", :last "Smith"}}
=> (def people (merge bob bill))
=> people
{:bob {:first "Bobby", :last "Martin"}, :bill {:first "Billy", :last "Smith"}}
=> (:bob people)
{:first "Bobby", :last "Martin"}
=> (:bill people)
{:first "Billy", :last "Smith"}
=> (get-in people [:bob :first])
"Bobby"
=> (get-in people [:bob :last])
"Martin"
=> (get-in people [:bill :first])
"Billy"
=> (get-in people [:bill :last])
"Smith"
It’s difficult to overstate the value of what you just read. Make sure you look it over carefully and understand it…but it get’s better.
=> (assoc-in people [:bob :first] "Robert")
{:bob {:first "Robert", :last "Martin"},
:bill {:first "Billy", :last "Smith"}}
=> (def people2 (-> people
(assoc-in [:bob :first] "Robert")
(assoc-in [:bill :first] "William")))
=> people2
{:bob {:first "Robert", :last "Martin"},
:bill {:first "William", :last "Smith"}}
=> (def people3 (-> people2
(assoc-in [:bob :age] 72)
(assoc-in [:bill :age] 61)))
=> people3
{:bob {:first "Robert", :last "Martin", :age 72},
:bill {:first "William", :last "Smith", :age 61}}
=> (update-in people3 [:bob :age] inc)
{:bob {:first "Robert", :last "Martin", :age 73},
:bill {:first "William", :last "Smith", :age 61}}
=> (reduce #(update-in %1 [(key %2) :age] inc) people3 people3)
{:bob {:first "Robert", :last "Martin", :age 73},
:bill {:first "William", :last "Smith", :age 62}}
Study that last one very carefully. When you understand why there are two people3 arguments, you will have achieved much — grasshopper.
1. Bank
Write a function named process-transactions that takes a map of accounts and a list of transactions, and applies all the transactions to the accounts. For example:
(process-transactions
{1 {:balance 0 :name "bob"}
2 {:balance 100 :name "bill"}}
[{:type :deposit :account 1 :amount 100}
{:type :withdrawal :account 2 :amount 50}
{:type :interest :rate 1/100}])
Should return:
{1 {:balance 101 :name "bob"}
2 {:balance 101/2 :name "bill"}}
Note that the interest transaction applies to all the accounts in the map.
2. Risk
A battle in the game of Risk is when two armies face off against one another. Each army has a number of units it can use in the battle.
The battle uses dice. The attacker rolls 1-3 dice, and then the defender rolls 1-2. Attackers will role one less than the number of units they have — up to 3. Defenders will 1 if they only have one unit, otherwise 2.
The highest roll of each is compared. Ties go to the defender. The losing side loses one unit. Then if both rolled two dice the second highest roll of each is compared and the same decision rule applies.
Write a function named play-risk that takes a game and a list of die rolls and returns the end result of the game. Given the game {:attackers 10 :defenders 10}, and the following list of die rolls, the result should be {:attackers 2 :defenders 0}.
[1 1 1 3 3
1 1 6 5 1
6 6 1 5 5
3 4 4 5 4
3 5 4 6 2
3 3 6 5 4
5 6 3 4
4 5 2 3
3 3 6
6 2]
You will likely need the sort, and perhaps even the reverse, function to complete this exercise.
Notice the user=> prompt below. In previous chapters I trimmed that off to avoid confusion; but from now on I’ll leave it there…because it’s time to talk about namespaces.
user=> :this
:this
user=> ::this
:user/this
user=> (= :this ::this)
false
user=> (= ::this ::this)
true
user=> (= :user/this ::this)
true
user=> (namespace ::this)
"user"
user=> (namespace :this)
nil
Study the above carefully. From it you will infer that :this is a global keyword, but
:user/this is a keyword in the user namespace. You’ll also infer that if you are working in the user namespace then ::this is equivalent to :user/this.
So how do you know which namespace you are working in? The prompt user=> will tell you if you are typing at the REPL. In a program however you could check the *ns* global variable.
user=> *ns*
#object[clojure.lang.Namespace 0x7051777c "user"]
Now let’s change the namespace we are working in to bob.
user=> (ns bob)
nil
bob=> *ns*
#object[clojure.lang.Namespace 0xff6077 "bob"]
That was easy. Did you notice that the prompt changed to bob=>? Now let’s play with ::this again.
bob=> ::this
:bob/this
bob=> (= ::this :user/this)
false
bob=> (= ::this :bob/this)
true
bob=> (namespace ::this)
"bob"
OK, so now that we are in the bob namespace, ::this refers to :bob/this. Not only that, but all things defined with def are in that namespace too.
bob=> (def ONE 1)
bob=> (def ONE 1)
#'bob/ONE
bob=> ONE
1
bob=> bob/ONE
1
bob=> (ns user)
nil
user=> ONE
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: ONE in this context
user=> bob/ONE
1
user=> (ns bob)
nil
bob=> (defn x2 [x] (+ x x))
#'bob/x2
bob=> (x2 3)
6
bob=> (bob/x2 6)
12
bob=> (ns user)
nil
user=> (x2 3)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: x2 in this context
user=> (bob/x2 3)
6
As you can see the names of variables and functions are qualified with the namespace in which they are created. The (def ONE 1) invocation within the bob namespace created the variable bob/ONE. While within the bob namespace ONE refers to bob/ONE. Within another namespace, like user, ONE has no definition; but bob/ONE can still be accessed by using the fully qualified name. The same holds true for functions.
Perhaps this whole business about namespaces confuses you. Why do they exist? So let’s use them the way they were intended.
First, let’s create a namespace for some mathematical functions.
user=> (ns math)
nil
math=> (defn power [x n] (apply * (repeat n x)))
#'math/power
math=> (power 10 3)
1000
math=> (power 4 2)
16
math=> (defn square [x] (power x 2))
#'math/square
math=> (square 5)
25
Next let’s create a namespace for some statistical functions.
math=> (ns stat)
nil
stat=> (defn fac [x] (apply * (range 1 (inc x))))
#'stat/fac
stat=> (fac 3)
6
stat=> (fac 10)
3628800
stat=> (defn mean [& ns] (/ (reduce + ns) (count ns)))
#'stat/mean
stat=> (mean 3 4 5)
4
stat=> (apply mean [4 5 6 7])
11/2
The only surprise there is that [& ns] syntax. But it just means that ns contains the list of all arguments after the &.
So we have effectively created two libraries of functions, math and stat. Now let’s write a program to compute the root-mean-square using these two libraries.
stat=> (ns my-program (:require [stat :as s] [math :as m]))
nil
my-program=> (math/power 10 2)
100
my-program=> (m/power 10 2)
100
my-program=> (defn rms [& ns] (Math/sqrt (apply s/mean (map m/square ns))))
#'my-program/rms
my-program=> (rms 3 4 5)
4.08248290463863
Look closely at that ns statement because you’ll be using statements like it a lot. It creates the s alias for the stat namespace, and the m alias for the math namespace. This is just for convenience. As we’ll soon see sometimes its nice to use abbreviations instead of fully qualified names.
That :require within the ns command does a lot more than just create aliases. It can also load libraries from files. But before I show you that, let’s talk about sets.
user=> #{1}
#{1}
user=> (type #{1})
clojure.lang.PersistentHashSet
user=> (set [1 2 3])
#{1 3 2}
user=> (set [1 1 1])
#{1}
user=> (into #{} [1 2 3 4 5 4 3 2 1])
#{1 4 3 2 5}
user=> (into [] #{1 2 3 4 5 6})
[1 4 6 3 2 5]
As you can see, a set is like a list but does not allow duplicates and does not maintain the order of the elements. There is a special library of functions for working with sets called clojure.set. Let’s see how it works.
user=> (ns set-stuff (:require [clojure.set :as set]))
nil
set-stuff=> (set/union #{1 2 3} #{3 4 5})
#{1 4 3 2 5}
set-stuff=> (set/intersection #{1 2 3} #{3 4 5})
#{3}
set-stuff=> (set/intersection #{2 3 4} #{5 6 7})
#{}
set-stuff=> (set/difference #{1 2 3 4 5} #{3 4})
#{1 2 5}
set-stuff=> (set/subset? #{2 3} #{1 2 3 4 5})
true
set-stuff=> (set/subset? #{2 3} #{3 4 5})
false
set-stuff=> (set/superset? #{3 4 5 6 7} #{3 4 5})
true
There’s that :require again. This time it pulled in the clojure.set library from the standard catalog of libraries that comes along with Clojure. I’m sure you found no surprises in the above example. But perhaps this next one will surprise you a bit.
set-stuff=> (#{1 2 3} 1)
1
set-stuff=> (#{:this :that :the-other} :none)
nil
set-stuff=> (def colors #{:red :green :blue})
#'set-stuff/colors
set-stuff=> (when (colors :red) (prn "color"))
"color"
nil
set-stuff=> (when (colors :purple) (prn "color"))
nil
Sets act like functions that check the membership of their argument. If the member is found it is returned, otherwise it returns nil. And, remember, anything non-nil will act like true if used as a predicate. We call non-nil predicates truthy.
1. Letters
Write a function in the namespace letters named find-missing that takes a string and prints a string containing the letters of the alphabet that are missing from that first string. Assume all strings are lower case. Thus (find-missing “bob”) should return “acdefghijklmnpqrstuvwxyz”.
2. Primes
Write a function called find-primes in the namespace primes. This function should calculate the primes numbers up to and including it’s argument. For example
(find-primes 6) should return [2 3 5].
Consider the problem to be one of set differences. Create a set of all the numbers from 1..n, and then for each number removed its multiples. You may find the range function to be helpful. Check out (doc range).
user=> (defn lup [n] (if (zero? n) :done (lup (dec n))))
user=> (lup 4)
:done
This is equivalent to:
user=> (if (zero? 4) :done
(if (zero? 3) :done
(if (zero? 2) :done
(if (zero? 1) :done
(if (zero? 0) :done)))))
:done
That nesting requires memory. Each if calls the next, which calls the next. The eventual :done must return back through all those nested ifs. How much memory does this take? How much nesting is possible?
user=> (time (lup 10))
"Elapsed time: 0.019973 msecs"
:done
user=> (time (lup 1000))
"Elapsed time: 1.690559 msecs"
:done
user=> (time (lup 10000))
Execution error (StackOverflowError) at user/lup (REPL:1).
null
Apparently ten thousands nested ifs is too many. But there is a way around this limit. Since the returned :done is not modified by any of the nested calls, we can ask the system not to remember the nesting and just return the final :done. This is called tail call optimization (TCO) and we invoke it with recur.
user=> (defn lup [n] (if (zero? n) :done (recur (dec n))))
user=> (time (lup 1000))
"Elapsed time: 0.227877 msecs"
:done
Apparently keeping track of the nesting took a fair bit of time. The recur version is much faster.
user=> (time (lup 10000))
"Elapsed time: 1.13428 msecs"
:done
user=> (time (lup 1000000))
"Elapsed time: 79.401391 msecs"
:done
user=> (time (lup 1000000000))
"Elapsed time: 3211.51927 msecs"
:done
A billion loops in 3.2 seconds! Three nanoseconds per loop. Not bad.
In the above, the recur simply jumped back to the top of the lup function. But there is another way to use recur.
user=> (defn fac [x] (loop [n x f 1] (if (zero? n) f (recur (dec n) (* f n)))))
The loop function initializes n with x, and f with 1. The recur jumps back to the loop.
user=> (fac 10)
3628800
user=> (fac 20)
2432902008176640000
user=> (fac 50)
Execution error (ArithmeticException) at java.lang.Math/multiplyExact (Math.java:1032).
long overflow
Regular integers have a limit. But we can always use BigInt.
user=> (fac 50N)
30414093201713378043612608166064768844377641568960512000000000000N
user=> (fac 100N)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000N
user=> (fac 1000N)
402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000N
Smile…
user=> (time (do (fac 10000N) :done))
"Elapsed time: 49.434411 msecs"
:done
user=> (time (do (fac 100000N) :done))
"Elapsed time: 2255.785047 msecs"
:done
user=> (count (str (fac 100000N)))
456574
Bigger smile…
user=> (defn fib [n] (if (<= n 2) 1 (+ (recur (dec n)) (recur (- n 2)))))
Syntax error (UnsupportedOperationException) compiling recur at (REPL:1:33).
Can only recur from tail position
Think about this one for a bit. The recur function can only be used if the nesting can be forgotten. This function the nesting for that + operation. Functions that depend on nesting in this way are known as recursive functions.
user=> (defn fib [n] (if (<= n 2) 1 (+ (fib (dec n)) (fib (- n 2)))))
#'user/fib
user=> (map fib (range 1 20))
(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181)
Nice! A bunch of Fibonacci numbers: fib(n)=(fib(n-1) + fib(n-2)).
user=> (time (fib 30))
"Elapsed time: 13.121772 msecs"
832040
user=> (time (fib 35))
"Elapsed time: 114.795155 msecs"
9227465
user=> (time (fib 40))
"Elapsed time: 1083.731026 msecs"
102334155
user=> (time (fib 42))
"Elapsed time: 2831.161555 msecs"
267914296
Wow! That timing is horrendous. Let’s do some analysis.
user=> (defn fib-ms [n]
(-
(- (System/currentTimeMillis[9])
(do (fib n) (System/currentTimeMillis)))))
#'user/fib-ms
user=> (fib-ms 30)
11
user=> (fib-ms 40)
1127
user=> (def fib-times (map fib-ms (range 35 45)))
#'user/fib-times
user=> fib-times
(115 161 260 422 674 1138 1770 2807 4527 7280)
If you type these commands into the REPL (as you should be doing!) then you likely got a surprise with those last too. The one that should have been slow was fast, and the one that should have been fast was slow. That’s due to laziness, which we’ll explore later.
user=> (map float (map #(/ %2 %1) fib-times (rest fib-times)))
(1.4 1.6149068 1.6230769 1.5971564 1.6884273 1.5553603 1.5858757 1.6127539 1.608129)
So each subsequent integer takes ~1.6 times longer than the last. Yikes! Let’s do some counting.
user=> (def fib-count (atom 0))
#'user/fib-count
user=> (type fib-count)
clojure.lang.Atom
An atom is a variable outside any function. We’ll spend more time on them later.
user=> (defn cfib [n]
(swap! fib-count inc)
(if (<= n 2) 1 (+ (cfib (dec n)) (cfib (- n 2)))))
#'user/cfib
That swap! Increments the fib-count atom.
user=> (cfib 20)
6765
user=> @fib-count
13529
Calculating the 20th Fibonacci number required 13,529 calls to cfib. Wow!
user=> (reset! fib-count 0)
0
user=> (cfib 21)
10946
user=> @fib-count
21891
Yeah, that makes sense, 12891÷13529 is about 1.6.
user=> (reset! fib-count 0)
0
user=> (cfib 30)
832040
user=> @fib-count
1664079
And so the number of recursive calls gets out of hand very quickly. Have you figured out why? Anyway, there’s a simple workaround.
user=> (def mlup (memoize lup))
#'user/mlup
user=> (time (mlup 1000000000))
"Elapsed time: 7567.874062 msecs"
:done
user=> (time (mlup 1000000000))
"Elapsed time: 0.052779 msecs"
:done
user=> (time (mlup 1000000001))
"Elapsed time: 7463.371179 msecs"
:done
user=> (time (mlup 1000000001))
"Elapsed time: 0.032837 msecs"
:done
Do you see how that works? The first time you call the memoized lup with x it executes lup and returns the result — but it remembers that result for x. The second (and every other) time you call the memoized lup with x it simply returns the remembered answer without executing lup.
user=> (def mfib
(memoize
(fn [n]
(if (<= n 2) 1N (+ (mfib (dec n))
(mfib (- n 2)))))))
user=> (mfib 1)
1N
user=> (mfib 10)
55N
user=> (mfib 20)
6765N
user=> (time (mfib 30))
"Elapsed time: 0.144727 msecs"
832040N
user=> (time (mfib 50))
"Elapsed time: 0.191239 msecs"
12586269025N
user=> (time (mfib 200))
"Elapsed time: 1.485109 msecs"
280571172992510140037611932413038677189525N
user=> (time (mfib 1000))
Execution error (StackOverflowError) at user/fn (REPL:1).
null
Memoizing made the calculation go a lot faster because we only had to calculate f(n) once and it would be remembered every other time it was needed. But we still have the problem of stacking up too much nesting. We can address this problem by iterating.
user=> (defn ifib
([n] (ifib n 0 1N))
([n f-1 f] (if (= n 1) f (recur (dec n) f (+ f-1 f)))))
#'user/ifib
We iterate by structuring the calculation so that the nesting can be forgotten, allowing us to use recur.
user=> (ifib 10)
55N
user=> (map ifib (range 1 20))
(1N 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N 233N 377N 610N 987N 1597N 2584N 4181N)
user=> (time (ifib 50))
"Elapsed time: 0.096049 msecs"
12586269025N
user=> (time (ifib 1000))
"Elapsed time: 0.243251 msecs"
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875N
user=> (time (ifib 2000))
"Elapsed time: 0.557079 msecs"
4224696333392304878706725602341482782579852840250681098010280137314308584370130707224123599639141511088446087538909603607640194711643596029271983312598737326253555802606991585915229492453904998722256795316982874482472992263901833716778060607011615497886719879858311468870876264597369086722884023654422295243347964480139515349562972087652656069529806499841977448720155612802665404554171717881930324025204312082516817125N
user=> (time (ifib 10000))
"Elapsed time: 3.024613 msecs"
33644764876431783266621612005107543310302148460680063906564769974680081442166662368155595513633734025582065332680836159373734790483865268263040892463056431887354544369559827491606602099884183933864652731300088830269235673613135117579297437854413752130520504347701602264758318906527890855154366159582987279682987510631200575428783453215515103870818298969791613127856265033195487140214287532698187962046936097879900350962302291026368131493195275630227837628441540360584402572114334961180023091208287046088923962328835461505776583271252546093591128203925285393434620904245248929403901706233888991085841065183173360437470737908552631764325733993712871937587746897479926305837065742830161637408969178426378624212835258112820516370298089332099905707920064367426202389783111470054074998459250360633560933883831923386783056136435351892133279732908133732642652633989763922723407882928177953580570993691049175470808931841056146322338217465637321248226383092103297701648054726243842374862411453093812206564914032751086643394517512161526545361333111314042436854805106765843493523836959653428071768775328348234345557366719731392746273629108210679280784718035329131176778924659089938635459327894523777674406192240337638674004021330343297496902028328145933418826817683893072003634795623117103101291953169794607632737589253530772552375943788434504067715555779056450443016640119462580972216729758615026968443146952034614932291105970676243268515992834709891284706740862008587135016260312071903172086094081298321581077282076353186624611278245537208532365305775956430072517744315051539600905168603220349163222640885248852433158051534849622434848299380905070483482449327453732624567755879089187190803662058009594743150052402532709746995318770724376825907419939632265984147498193609285223945039707165443156421328157688908058783183404917434556270520223564846495196112460268313970975069382648706613264507665074611512677522748621598642530711298441182622661057163515069260029861704945425047491378115154139941550671256271197133252763631939606902895650288268608362241082050562430701794976171121233066073310059947366875N
The moral to this story is: When possible iterate by using recur. If you can’t iterate then memoize. If you can’t memoize…well, then the best of luck to you.
Oh, and if you were wondering…
user=> (time (count (str (ifib 1000000))))
"Elapsed time: 11358.0141 msecs"
208988
1. Ballistics (a long time ago in a galaxy far far away.)
This problem is easier than it looks. Don’t be intimidated. It is, in fact, one of the problems that drove the development of the computer in the 1940s.
We have determined that the density of the air decreases with altitude according to the formula: -0.0000375085h3+0.0035692521h2-0.1132617585h+1.2225038068
Where h is the height above ground in 1000’s of feet.
The deceleration of air resistance in feet per second upon our cannonball is Cddv2, where d is the air density, v is the velocity, and Cd is 1/100,000. The downward acceleration of gravity is 32.174 feet per second2.
Our cannonball is fired from the ground with a vertical velocity of 1500 feet per second, and a horizontal velocity of 500 feet per second. When and where does the cannonball hit the ground, and what altitude did it reach?
Approximate this by dividing time into 1ms steps and track the position and velocities of the cannonball at each step. The ground is at height 0.
2. Sum of Fractions
It turns out that the infinite sum of consecutive fractions 1/1 + 1/2 + 1/3 + 1/4… is infinite. That means that any finite integer can be reached by adding up enough consecutive fractions. How many factions have to be added up to reach a sum greater than or equal to 10?
3. Word Wrap
For this problem you should check the docs of the subs and string/last-index-of functions.
Write the function (defn word-wrap [s n]…) that splits the string s into lines no longer than n. Break lines at the last possible space before n, but never allow a line to exceed n. The string s will contain only alphabetic letters, numbers, and spaces.
Example, if s is (without line ends):
“Four score and seven years ago our fathers brought forth upon this continent a new nation conceived in liberty and dedicated to the proposition that all men are created equal”
and if n is 20, then the output should be:
Four score and seven
years ago our
fathers brought
forth upon this
continent a new
nation conceived in
liberty and
dedicated to the
proposition that all
men are created
equal
Back in the first chapter I suggested that you type the clj command to start up a REPL. That command can do a lot more than just that. It can link together a set of modules and run them as a single program. Consider the following files:
- - - math.clj - - -
(ns math)
(defn power [x n]
(apply * (repeat n (bigint x))))
(defn rat->dec [r n]
(with-precision n (bigdec r)))
A simple little module that computes integral powers, and a utility function that converts a number to a big decimal with limited precision.
- - - stat.clj - - -
(ns stat)
(defn fac [n] (apply * (range 1N (inc n))))
A small module that calculates factorials.
- - - taylor.clj - - -
(ns taylor (:require math stat))
(defn term [x n]
(let [numerator (math/power x n)
denominator (stat/fac n)]
(/ numerator denominator)))
(defn exp [x n]
(let [terms (map (partial term x) (range 0 n))]
(reduce + terms)))
A lovely module that uses the previous modules to calculate the Taylor series for ex to a certain precision.
- - - expmain.clj - - -
(ns expmain (:require taylor math))
(defn -main [& args]
(prn (->> args
(map #(Integer/parseInt %))
(map #(taylor/exp % 30))
(map #(math/rat->dec % 30)))))
And a main program that takes arguments from the command line, turns them into integers, uses the taylor module to computes ex to 30 places for of those integers, and then prints the results as 30 digit decimals.
Now let’s arrange these files into the following directory structure:
+-letsGetModular
+---src
+-----math.clj
+-----stat.clj
+-----taylor.clj
+-----expmain.clj
Now, from within the letsGoModular directory:
>clj -M -m expmain 1 2 3
(2.71828182845904523536028747135M 7.38905609893065022723042313405M 20.0855369231876677400694616553M)
When the clj command is executed from within a directory that contains a src directory, then it will use all the .clj files in that src directory. The -M -m expmain arguments tell clj to execute the -main function within the expmain.clj file. The 1 2 3 arguments are passed, as a list, into the args parameter of -main.
1. Path Length
Create a module named geometry.clj that contains a function distance that takes two points of the form [x y] and returns the distance between those points using the distance formula sqrt((x1-x2)2+(y1-y2)2).
Create another module named pathlength.clj that contains a function length that takes a list of points along a path, which are of the form [x y], and calculates the distance of the whole path. Assume that x and y are integers.
Created a third module named pathmain, that has a -main function that calculates the length of a path entered as follows:
>clj -M -m pathmain 3,4 4,5 6,8
5.0197648378370845
Hint: You might want to look at string/split.
By now you’ve probably gotten tired of unpacking individual elements of a vector with a lot of first and second calls.
user=> (let [[x y] [1 2]] (prn x y))
1 2
nil
user=> (defn square [x] (* x x))
#'user/square
user=> (defn distance [[x1 y1] [x2 y2]]
(Math/sqrt (+ (square (- x1 x2)) (square (- y1 y2)))))
#'user/distance
user=> (distance [0 0] [1 1])
1.4142135623730951
user=> (distance [0 0] [1 0])
1.0
This is called destructuring. Stop now and play around with it. Notice how deep you can go. Try using the & to collect left over elements. And then get ready because this is going to get lit.
user=> (def bob {:first-name "Bob" :last-name "Martin" :age 71})
#'user/bob
user=> bob
{:first-name "Bob", :last-name "Martin", :age 71}
user=> (let [{name :first-name birth :dob :or {birth :none}} bob] (prn name birth))
"Bob" :none
nil
That’s nifty right? Play around with that :or. This next one is even better.
user=> (let [{:keys [first-name age]} bob] (prn first-name age))
"Bob" 71
nil
This works really well so long as the keywords and the variables have the name spelling.
user=> (defn hi [{:keys [first-name last-name] :as person}]
(println "Hello" last-name "," first-name "," (:age person)))
#'user/hi
user=> (hi bob)
Hello Martin , Bob , 71
nil
Of course you don’t have to use keywords if you don’t want to:
user=> (def sun {"name" "Sol" "mass" 2e30 "diameter" 1.4e6 "distance" 1.5e8})
#'user/sun
user=> sun
{"name" "Sol", "mass" 2.0E30[10], "diameter" 1400000.0, "distance" 1.5E8}
user=> (let [{:strs [name distance]} sun]
(printf[11] "%s is %.0f km away\n" name distance))
Sol is 150000000 km away
nil
Yeah, you can use strings as the keys if you like — though keywords are better in most cases.
user=> (def trixie {:dog/breed :Chihuahua :dog/age 13 :dog/color :tan})
#'user/trixie
user=> trixie
#:dog{:breed :Chihuahua, :age 13, :color :tan}
Notice the :dog in front of the {? That tells you that the keywords in the map are all in the dog namespace.
user=> (let [{:dog/keys [breed color]} trixie] (prn "Trixie" breed color))
"Trixie" :Chihuahua :tan
And that’s how you destructure namespaced keywords.
user=> (defn some-func [arg & {:keys [x y]}] (prn arg x y))
#'user/some-func
user=> (some-func 99 :x 1 :y 2)
99 1 2
nil
And that’s how you pass an implicit map (a map without the braces) into a function.
1. Path Length 3D
Write the function path-length that takes a list of 3D points that defines a path and calculates the length of that path.
For example (path-length [0 0 0] [0 0 1] [0 1 1] [1 1 1]) should equal 3.
user=> (Integer/parseInt "21")
21
user=> (Double/parseDouble "3.14")
3.14
These are nice when you want to turn a string into a number.
user=> (format "Hello %s you are %d." "Bob" 71)
"Hello Bob you are 71."
The format function takes many different % codes to format the arguments.
user=> (format "Name: %-20s Height: %3.1f meters." "William Mitchell" 2.05)
"Name: William Mitchell Height: 2.1 meters."
user=> (format "Name: %-20s Height: %3.1f meters." "Robert Martin" 1.9)
"Name: Robert Martin Height: 1.9 meters."
user=> (format "Name: %20s Height: %3.1f meters." "Trixie" 0.1)
"Name: Trixie Height: 0.1 meters."
user=> (format "Name: %20s Height: %3.2f meters." "Tulip" 0.08)
"Name: Tulip Height: 0.08 meters."
user=> (format "Name: %20s Height: %4.2f meters." "Tulip" 0.08)
"Name: Tulip Height: 0.08 meters."
user=> (format "Name: %20s Height: %5.2f meters." "Tulip" 0.08)
"Name: Tulip Height: 0.08 meters."
Using numbers after the % gives you a lot of control over spacing.
You can learn more about this at:
http://download.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html
user=> (subs "hello" 1)
"ello"
user=> (subs "hello" 1 2)
"e"
The subs function is short for substring.
user=> (require '[clojure.string :as string])
nil
user=> (string/join "," ["me" "you"])
"me,you"
user=> (string/split-lines "hello\nthere")
["hello" "there"]
user=> (string/index-of "This is text" "is")
2
The \n between hello and there is an escape character representing a line end. There are other escape characters like that.
user=> (type #"this")
java.util.regex.Pattern
That leading # turns a string into a regular expression. As you’ll see, regular expressions match patterns.
user=> (string/split "1,2,3, 31" #",\s*")
["1" "2" "3" "31"]
The #”,\s*” pattern matches a comma followed by any number of spaces, including zero spaces. The \s character means any whitespace character like space or tab.
user=> (string/replace "this is text" #"\s+" ".")
"this.is.text"
In this case the #”\s+” matches any string of whitespace with at least one whitespace character in it.
user=> (re-find #"\d+" "This is 99...")
"99"
user=> (re-seq #"\d+" "so 9 > 8 < 99")
("9" "8" "99")
I’m sure you realized that \d matches any digit. Y
user=> (re-matches #"Hello!*" "Hell")
nil
user=> (re-matches #"Hello!*" "Hello")
"Hello"
user=> (re-matches #"Hello!*" "Hello!!!!!!!!")
"Hello!!!!!!!!"
user=> (re-matches #"Hello!*" "Hello!!!!!!!!x")
nil
The re-matches function returns nil if the string does not match, otherwise it returns the matched string.
user=> (re-matches #".X" "")
nil
user=> (re-matches #".X" "X")
nil
user=> (re-matches #".X" "aX")
"aX"
user=> (re-matches #".X" "bX")
"bX"
user=> (re-matches #"\.X" "bX")
nil
user=> (re-matches #"\.X" ".X")
".X"
In a regular expression a . matches any character at all, and \. Matches just a ..
user=> (re-matches #"Time (\d+):(\d+)." "Time 3:15.")
["Time 3:15." "3" "15"]
The re-matches function also deals with parentheses in the regular expression by extracting the match within the parentheses.
Regular expressions are a big topic. You can find out more about them in the following URL. Don’t be daunted by the complexity of that page, and don’t try to read it all right now.
https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html
1. Receipt
Calling (slurp items.txt) returns a string containing the entire contents of the items.txt file.
Write a program that reads in items.txt and prints a receipt as follows.
- - - items.txt
Product: chips Price:2.89 Quantity: 2
Product: Coke 12 pack Price:6.99 Quantity: 4
Product: Newspaper Price:1.49 Quantity: 1
Product: Cheddar Cheese Price:6.87 Quantity: 1
Product: Lifesavers Price:.50 Quantity: 7
- - - The receipt
chips X2 @2.89 = 5.78 | 5.78
Coke 12 pack X4 @6.99 = 27.96 | 33.74
Newspaper X1 @1.49 = 1.49 | 35.23
Cheddar Cheese X1 @6.87 = 6.87 | 42.10
Lifesavers X7 @0.50 = 3.50 | 45.60
~/junk/libraries >cat deps.edn
{:deps
{clojure.java-time/clojure.java-time {:mvn/version "1.4.2"}}
}
If that looks like a Clojure hashmap to you, you’re not wrong. The .edn file suffix indicates that the deps.edn file contains a Clojure data structure.
~/junk/libraries >clj
Downloading: clojure/java-time/clojure.java-time/1.4.2/clojure.java-time-1.4.2.jar from clojars
Clojure 1.11.1
user=>
Starting clj in a directory that contains a deps.edn file causes clj to reach out and download the mentioned libraries.
user=> (require '[java-time.api :as jt])
nil
See clojars.org and github.com/dm3/clojure.java-time for more information about the clojure.java-time library. Let’s use some of it’s facilities now.
user=> (jt/instant)
#object[java.time.Instant 0x7f27f59b "2024-05-07T15:16:55.527525Z"]
user=> (str (jt/instant))
"2024-05-07T15:17:03.484606Z"
user=> (type (jt/instant))
java.time.Instant
user=> (str (jt/local-date))
"2024-05-07"
user=> (str (jt/local-time))
"10:17:41.426758"
user=> (jt/format "MM/dd" (jt/local-date))
"05/07"
user=> (jt/local-date "MM/dd/yyyy" "05/12/1977")
#object[java.time.LocalDate 0x320be73 "1977-05-12"]
user=> (def a-date (jt/local-date "MM/dd/yyyy" "05/12/1977"))
#'user/a-date
user=> (jt/format "dd MMM, yy" a-date)
"12 May, 77"
You can read more about those formatting codes in:
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
user=> (def agile (jt/instant "2001-02-11T00:00:00.0Z"))
#'user/agile
user=> (def now (jt/instant))
#'user/now
user=> (jt/time-between agile now :days)
8486
Now let’s use the clojure.math.combinatorics library.
user=> (require '[clojure.math.combinatorics :as combo])
Execution error (FileNotFoundException) at user/eval3407 (REPL:1).
Could not locate clojure/math/combinatorics__init.class, clojure/math/combinatorics.clj or clojure/math/combinatorics.cljc on classpath.
Oops. I forgot to add the library to deps.edn.
~/junk/libraries >cat deps.edn
{:deps
{clojure.java-time/clojure.java-time {:mvn/version "1.4.2"}
org.clojure/math.combinatorics {:mvn/version "0.3.0"}}
}
~/junk/libraries >clj
Downloading: org/clojure/math.combinatorics/0.3.0/math.combinatorics-0.3.0.pom from central
Downloading: org/clojure/math.combinatorics/0.3.0/math.combinatorics-0.3.0.jar from central
Clojure 1.11.1
user=> (require '[clojure.math.combinatorics :as combo])
nil
That’s better. Now let’s play.
user=> (combo/permutations ['a 'b 'c])
((a b c) (a c b) (b a c) (b c a) (c a b) (c b a))
user=> (combo/combinations [:a :b :c :d] 2)
((:a :b) (:a :c) (:a :d) (:b :c) (:b :d) (:c :d))
Cool! Now let’s go get the complex arithmetic library!
~/junk/libraries >cat deps.edn
{:deps
{clojure.java-time/clojure.java-time {:mvn/version "1.4.2"}
org.clojure/math.combinatorics {:mvn/version "0.3.0"}
complex/complex {:mvn/version "0.1.12"}
}
}
~/junk/libraries >clj
Downloading: complex/complex/0.1.12/complex-0.1.12.pom from clojars
Downloading: complex/complex/0.1.12/complex-0.1.12.jar from clojars
Clojure 1.11.1
user=> (require '[complex.core :as c])
WARNING: abs already refers to: #'clojure.core/abs in namespace: complex.core, being replaced by: #'complex.core/abs
nil
user=> (c/complex 1 1)
#object[org.apache.commons.math3.complex.Complex 0x3ee39da0 "(1.0, 1.0)"]
user=> (str (c/complex 1 1))
"(1.0, 1.0)"
user=> (c/conjugate (c/complex 2 3))
#object[org.apache.commons.math3.complex.Complex 0x75201592 "(2.0, -3.0)"]
user=> (c/* (c/complex 2 3) (c/complex 3 4))
#object[org.apache.commons.math3.complex.Complex 0x438bad7c "(-6.0, 17.0)"]
user=> (def pi-i (c/complex 0 Math/PI))
#'user/pi-i
user=> pi-i
#object[org.apache.commons.math3.complex.Complex 0x2899a8db "(0.0, 3.141592653589793)"]
user=> (c/pow Math/E pi-i)
#object[org.apache.commons.math3.complex.Complex 0x1162410a "(-1.0, 1.2246467991473532E-16)"]
So, eiπ=-1. Good to know.
1. Print JSON
Use the org.clojure/data.json library to convert the following data structure to JSON. You will find that library here: https://github.com/clojure/data.json.
{:first-name "Robert"
:last-name "Martin"
:age 71
:children ["Angela" "Micah" "Gina" "Justin"]}
>ls
deps.edn spec src
>cat deps.edn
{:deps
{org.clojure/clojure {:mvn/version "1.11.1"}}
:aliases
{:spec
{:main-opts ["-m" "speclj.main" "-c"]
:extra-deps {speclj/speclj {:mvn/version "3.4.6"}}
:extra-paths ["spec"]}}
}
The deps.edn file is saying that we will be using Clojure version 1.11.1. It also describes an alias named spec to make it easier to run our tests. That alias will use speclj version 3.4.6 and will consider the spec subdirectory as part of the project.
––– spec/letsTest_spec.clj –––
(ns letsTest-spec
(:require
[speclj.core :refer :all]
[letsTest :refer :all]))
(describe "letsTest"
(it "computes averages"
(should= 0 (average))))
––– src/letsTest.clj –––
(ns letsTest)
(defn average [])
––––––––––––––––––––––––
>clj -M:spec -a
1) letsTest computes averages
Expected: 0
got: nil (using =)
/letsTest/spec/letsTest_spec.clj:7
Finished in 0.00019 seconds
1 examples, 1 failures
The clj -M:spec -a command executes the alias which runs the tests. The -a argument invokes autotest, which will automatically rerun the tests any time one of the files is changed.
––– src/letsTest.clj –––
(ns letsTest)
(defn average []
0)
––––––––––––––––––––––––
letsTest
- computes averages
Finished in 0.00002 seconds
1 examples, 0 failures
The test passed. Let’s add another constraint.
––– spec/letsTest_spec.clj –––
(describe "letsTest"
(it "computes averages"
(should= 0 (average))
(should= 1 (average 1))
––– src/letsTest.clj –––
(ns letsTest)
(defn average [& ns]
0)
––––––––––––––––––––––––
1) letsTest computes averages
Expected: 1
got: 0 (using =)
/letsTest/spec/letsTest_spec.clj:8
Finished in 0.00016 seconds
1 examples, 1 failures
1. Test
Add a few more tests and make them pass.
––– deps.edn –––
{:deps
{quil/quil
{:mvn/version “4.3.1563”}}
}
________________
This will bring in the quil library which provides a framework for drawing pretty pictures on the screen — like this.
What that picture cannot show is that the circle is traveling around in a spiral path, changing both radius and color as it glides.
––– gui.clj –––
(ns gui
(:require [quil.core :as q]
[quil.middleware :as m]))
(defn setup []
(q/frame-rate 30)
(q/color-mode :hsb)
{:color 0
:angle 0
:distance 150
:size 100})
The screen will update 30 times per second. Color will be specified in [hue, saturation, brightness][12] triplets. And the initial state of the circle will have a hue of 0 (red), an angle from the origin of 0, a distance from the origin of 150 pixels, and a diameter of 100 pixels.
(defn update-state [{:keys [color angle distance size]}]
{:color (mod (+ color 0.7) 255)
:angle (+ angle 0.1)
:distance (if (< distance -150) 150 (- distance 1))
:size (if (< size -100) 100 (- size 1))})
This function is called 30 times per second. It’s input is the state of the circle. It’s output is the new state of the circle. As you can see the color, angle, distance, and size of the circle are all modified incrementally.
(defn draw-state [{:keys [color angle distance size]}]
(q/background 240)
(q/fill color 255 255)
(let [size (abs size)
distance (abs distance)
x (* distance (q/cos angle))
y (* distance (q/sin angle))]
(q/with-translation [(/ (q/width) 2)
(/ (q/height) 2)]
(q/ellipse x y size size))))
This function is also called 30 times per second. It sets the background to a soft grey (240), and the fill color of any drawn shape to the hue of the circle. Then it corrects for any negatives in the size and distance, computes the (x,y) coordinates of the circle’s center, sets the origin of the screen to the center of the screen, and then draws the circle.
(q/defsketch gui
:title "You spin my circle right round"
:size [500 500]
:setup setup
:update update-state
:draw draw-state
:features [:keep-on-top]
:middleware [m/fun-mode])
This is a call to the defsketch function. It starts up the quil framework, creates the sketch window, calls setup, and then starts calling update-state and draw-state 30 times per second.
(defn -main [& args]
(println "gui has begun."))
This is the main function that gets called when you invoke clj -M -m gui.
1. Fireworks.
Write a quil program that simulates fireworks. Show a fireworks rocket climbing into the sky and then bursting into many smaller stars that fall and finally fade away.
——— deps.edn ———
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
ring/ring-core {:mvn/version "1.11.0"}
ring/ring-jetty-adapter {:mvn/version "1.11.0"}
ring/ring-defaults {:mvn/version "0.4.0"}
compojure/compojure {:mvn/version "1.7.1"}
hiccup/hiccup {:mvn/version "2.0.0-RC3"}}}
There are several Clojure libraries that help with creating websites. The ring library handles the low-level communications between the browser and the web program. The compojure library helps you decode the urls. The hiccup library helps you create the HTML to send back to the browser.
(ns converter
(:require
[ring.adapter.jetty :as j]
[compojure.core :refer [defroutes GET]]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults
site-defaults]]
[hiccup2.core :as h]))
First we load in all the libraries we need, and refer the appropriate functions and constants.
(defn ftoc [fahr-s]
(let [fahr (Double/parseDouble fahr-s)
celsius (* 5/9 (- fahr 32))]
(str (h/html [:h1 "FTOC"] [:br] fahr-s "°F = " celsius "°C"))))
The ftoc function takes in a string that represents a number of degrees Fahrenheit. It parses that string into a Double and then does the math to convert it to degrees Celsius. Finally it uses hiccup to create an HTML display of the conversion.
(defroutes app-routes
(GET "/ftoc/:fahr" [fahr] (ftoc fahr))
(route/not-found "Not Found"))
The defroutes macro, from the compojure library, allows you to specify the various urls, and their contents, that your website should handle.
(def handler
(wrap-defaults app-routes site-defaults))
As you’ll see below, the ring library invokes this handler. The handler is invoked every time there is a web request. The url from that request is passed to the compojure routes defined in the defroutes macro.
(defn -main [& args]
(j/run-jetty handler {:port 3000}))
This is the main program. You can invoke it with the command: clj -M -m converter. That will start up your web server on port 3000. To access your web program use a browser to go to localhost:3000/ftoc/32. It should print out:
32°F = 0.0°C
I recommend you play with the code a bit to gain some understanding of all the magic symbols. Don’t be daunted, it’s not too hard to figure out.
1. Celsius to Fahrenheit.
Add the /ctof route to convert Celsius to Fahrenheit.
1. Albert.
=> (defn albert [m] (* m 2999792458 299792458)
=> (albert 1)
899315154473681764
2. Mix it up.
=> (defn mix [s1 s2] (apply str (map str s1 s2)))
=> (mix "Robert" "Martin")
"RMoabretritn"
3. Average.
=> (defn mean [l] (/ (apply + l) (count l)))
=> (mean [1 2])
3/2
=> (double (mean [1 2]))
1.5
4. Power.
=> (defn pow [n e] (apply * (repeat e n)))
=> (pow 2 5)
32
=> (pow 2 0)
1
5. Filter.
=> (defn pos-neg [l] (concat (filter pos? l) (filter zero? l) (filter neg? l)))
=> (pos-neg [6 -1 0 4 -2 0])
(6 4 0 0 -1 -2)
6. Pythagorus.
=> (defn pythag? [a b c]
(= (* c c) (+ (* a a) (* b b))))
=> (pythag? 3 4 5)
true
=> (pythag? 1 2 3)
false
1. Quad.
(defn quad [a b c]
(cond
(zero? a)
(/ (- c) b)
(neg? (- (* b b) (* 4 a c)))
"imaginary"
(zero? (- (* b b) (* 4 a c)))
(/ (- b) (* 2 a))
:else
[(/ (+ (- b) (Math/sqrt (- (* b b) (* 4 a c)))) (* 2 a))
(/ (- (- b) (Math/sqrt (- (* b b) (* 4 a c)))) (* 2 a))]))
2. Fizzbuzz.
(defn fizzbuzz [n]
(for [x (range 1 (inc n))]
(cond
(zero? (rem x 15))
"fizzbuzz"
(zero? (rem x 5))
"buzz"
(zero? (rem x 3))
"fizz"
:else
x)))
3. Distances.
(def points [["O" 0 0] ["A" 1 1] ["B" 1 2] ["C" 2 3] ["D" 4 5]])
(defn square [x] (* x x))
(defn distance [x1 y1 x2 y2]
(Math/sqrt (+ (square (- x1 x2)) (square (- y1 y2)))))
(defn distances [points]
(doseq [p1 points p2 points :when (not= p1 p2)]
(prn
(str (first p1) "-" (first p2))
(distance (nth p1 1) (nth p1 2) (nth p2 1) (nth p2 2)))))
These exercises use some of the functions from the chapter, like sin-term-sign. They are not repeated in the solutions presented here.
1. Arctangent.
(defn arctan-term [x n]
(* (sin-term-sign n)
(/ (pow x n) n)))
(defn arctan [x n]
(let [ns (range 1 n 2)
terms (map (fn [n] (arctan-term x n)) ns)]
(apply + terms)))
=> (* 4.0 (arctan 1 2000))
3.140592653839793
2. Generate π.
(defn arctan-term [x n]
(* (sin-term-sign n)
(/ 1.0 n)))
=> (* 4.0 (arctan 1 2000000))
3.1415916535897743
3. Seriously π.
(defn arctan-term [x n]
(* (sin-term-sign n)
(/ (pow x n) n)))
(defn arctan [x n]
(let [ns (range 1 n 2)
terms (map (fn [n] (arctan-term x n)) ns)]
(apply + terms)))
(defn pi-gen [n]
(let [t1 (* 12 (arctan 1/38 n[13]))
t2 (* 20 (arctan 1/57 n))
t3 (* 7 (arctan 1/239 n))
t4 (* 24 (arctan 1/268 n))
pi-4 (+ t1 t2 t3 t4)]
(with-precision n (bigdec (* 4 pi-4)))))
=> (pi-gen 1000)
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628
03482534211706798214808651328230664709384460955058223172535940812848111745028410270193
85211055596446229489549303819644288109756659334461284756482337867831652712019091456485
66923460348610454326648213393607260249141273724587006606315588174881520920962829254091
71536436789259036001133053054882046652138414695194151160943305727036575959195309218611
73819326117931051185480744623799627495673518857527248912279381830119491298336733624406
56643086021394946395224737190702179860943702770539217176293176752384674818467669405132
00056812714526356082778577134275778960917363717872146844090122495343014654958537105079
22796892589235420199561121290219608640344181598136297747713099605187072113499999983729
78049951059731732816096318595024459455346908302642522308253344685035261931188171010003
13783875288658753320838142061717766914730359825349042875546873115956286388235378759375
1957781857780532171226806613001927876611195909216420199M
1. Unthread.
I hope you struggled over this one. It’s hard to unthread sigma, isn’t it. In the end I compromised with the following. I’ll leave it to you to decide whether the definition of deviance employs an anonymous function or not.
(defn sigma [l]
(let [u (mean l)
deviance (fn [x] (- x u))]
(Math/sqrt (mean (map square (map deviance l))))))
2. Popularity.
(defn popularity [l]
(double (reduce #(+ %2 (/ %1 2)) l)))
=> (popularity [100 150 90 200 120 150 90])
231.875
3. Ships.
I hope you used a text editor to type your program in. This is my solution:
(defn add-vector [v1 v2]
[(+ (first v1) (first v2))
(+ (second v1) (second v2))])
(defn move-ship [ship]
(let [position (first ship)
velocity (second ship)
velocity (if (nil? velocity) [0 0] velocity)
position (add-vector position velocity)]
[position velocity])
)
(defn thrust-ship [ship thrust]
(let [position (first ship)
velocity (second ship)
velocity (if (nil? velocity) [0 0] velocity)
velocity (add-vector velocity thrust)]
[position velocity]))
(defn tick [ship thrust]
(-> ship (thrust-ship thrust) move-ship))
(defn ticks [ship thrusts]
(reduce tick ship thrusts))
=> (ticks [[0 0]] [[1 1] [0 0] [2 0] [-1 2] [-1 -1]])
[[8 8] [1 2]]
1. Bank
(defn deposit [account amount]
(update account :balance + amount))
(defn withdraw [account amount]
(update account :balance - amount))
(defn pay-interest [account rate]
(update account :balance * (+ 1 rate)))
(defn process-transaction [accounts transaction]
(let [t-type (:type transaction)
t-account (:account transaction)]
(condp = t-type
:deposit
(update accounts t-account deposit (:amount transaction))
:withdrawal
(update accounts t-account withdraw (:amount transaction))
:interest
(reduce (fn [accounts map-entry]
(assoc accounts
(first map-entry)
(pay-interest
(second map-entry)
(:rate transaction)))) {} accounts))))
(defn process-transactions [accounts transactions]
(reduce process-transaction accounts transactions))
2. Risk
(defn can-attack-more? [game]
(let [attackers (:attackers game)
max-attack-dice (min 3 (dec attackers))
attack-dice (:attack-dice game)]
(< (count attack-dice) max-attack-dice)))
(defn can-defend-more? [game]
(let [defenders (:defenders game)
max-defence-dice (min 2 defenders)
defence-dice (:defence-dice game)]
(< (count defence-dice) max-defence-dice)))
(defn fight [game pair]
(let [attack-die (first pair)
defence-die (second pair)]
(if (> attack-die defence-die)
(update game :defenders dec)
(update game :attackers dec))))
(defn battle [game]
(let [attack-dice (reverse (sort (:attack-dice game)))
defence-dice (reverse (sort (:defence-dice game)))
pairs (map (fn [a b] [a b]) attack-dice defence-dice)
game (assoc game :attack-dice [] :defence-dice [])]
(reduce fight game pairs)
))
(defn handle-die [game die]
(let [game (cond
(can-attack-more? game)
(update game :attack-dice conj die)
(can-defend-more? game)
(update game :defence-dice conj die)
:else game)]
(if (can-defend-more? game)
game
(battle game))
))
(defn play-risk [game dice]
(reduce handle-die game dice))
1. Letters
(ns letters
(:require [clojure.set :as set]))
(defn find-missing [s]
(let [alphabet "abcdefghijklmnopqrstuvwxyz"
alph-set (set alphabet)
s-set (set s)
missing (sort[14] (set/difference alph-set s-set))]
(apply str missing)))
2. Primes
(ns primes
(:require [clojure.set :as set]))
(defn find-primes [n]
(let [ns (range 2 (inc n))
primes (reduce
(fn [ps x]
(set/difference
ps
(set (range (* 2 x) (inc n) x))))
(set ns)
ns)]
(sort primes)))
1. Ballistics
(ns ballistics)
(def G 32.17405)
(def TIC 1/1000)
(def CD 1/100000)
(defn density [h]
(reduce + [1.2225038068
(* h -0.1132617585)
(* h h 0.0035692521)
(* h h h -0.0000375085)]))
(defn gravity [v]
(- v (* G TIC)))
(defn drag [h v]
(let [av (abs v)
sv (pos? v)
av (- av (* CD (density (/ h 1000)) av av TIC))]
(if sv av (- av))))
(defn track []
(loop [x 0 y 0 xv 500 yv 1500 t 0 maxy 0]
(if (and (> t 0) (<= y 0))
(prn (float t) [x y] [xv yv] maxy)
(recur (+ x (* TIC xv)) (+ y (* TIC yv))
(drag y xv)
(gravity (drag y yv))
(+ t TIC)
(max maxy y)))))
2. Sum of Fractions
(ns sum_fractions)
(defn sum-fractions [n]
(loop [denom 1
sum 0]
(if (>= sum n)
(println "denominator: " denom)
(recur (inc denom) (+ sum (/ 1 denom))))))
3. Word Wrap
(ns word_wrap
(:require [clojure.string :as string]))
(defn wrap [s n]
(if (<= (count s) n)
s
(let [last-space (string/last-index-of s " " n)
split-point (if (nil? last-space) n last-space)
join-point (if (nil? last-space) n (inc last-space))]
(str (subs s 0 split-point) "\n" (wrap (subs s join-point) n)))))
1. Path Length
- - - geometry.clj - - -
(ns geometry)
(defn distance [p1 p2]
(let [x1 (first p1)
y1 (second p1)
x2 (first p2)
y2 (second p2)
h (- x1 x2)
v (- y1 y2)]
(Math/sqrt (+ (* h h) (* v v)))))
- - - pathlength.clj - - -
(ns pathlength
(:require [geometry :as g]))
(defn add-segment [sum-and-last next]
(let [sum (first sum-and-last)
last (second sum-and-last)
d (g/distance last next)]
[(+ sum d) next]))
(defn length [points]
(first (reduce add-segment [0 (first points)] (rest points))))
- - - pathmain.clj - - -
(ns pathmain
(:require
[clojure.string :as string]
[pathlength :refer :all]))
(defn make-point [s]
(let [ps (string/split s #",")]
(map #(Integer/parseInt %) ps)))
(defn -main [& args]
(println (length (map make-point args))))
1. Path Length 3D
(ns pathlength3d)
(defn square [x] (* x x))
(defn distance [[x1 y1 z1] [x2 y2 z2]]
(Math/sqrt (+ (square (- x1 x2))
(square (- y1 y2))
(square (- z1 z2)))))
(defn path-length [points]
(let [starts (drop-last points)
destinations (rest points)
distances (map distance starts destinations)]
(reduce + distances)))
1. Receipt
(ns receipt
(:require [clojure.string :as string]))
(defn parse-line [line]
(let [match (re-matches
#"\s*Product:\s*(.+)\s+Price:\s*(\d*\.\d\d)\s+Quantity:\s*(\d+)\s*"
line)
[product price quantity] (rest match)
price (Double/parseDouble price)
quantity (Integer/parseInt quantity)
parsed-line {:product product :price price :quantity quantity}]
parsed-line))
(defn get-items [file-name]
(let [lines (string/split-lines (slurp file-name))]
(map parse-line lines)))
(defn make-receipt [items]
(loop [items items
subtotal 0
receipt []]
(if (empty? items)
(string/join "\n" receipt)
(let [{:keys [price quantity product]} (first items)
amount (* price quantity)
subtotal (+ amount subtotal)
receipt-line (format "%20s X%-2d @%-5.2f =%7.2f |%7.2f"
product quantity price amount subtotal)]
(recur (rest items)
subtotal
(conj receipt receipt-line))))))
1. Print JSON
–– deps.edn ––
{:deps {org.clojure/data.json {:mvn/version "2.5.0"}}}
––––––––––––––
(ns json
(:require [clojure.data.json :as json]))
(defn create-report []
(json/write-str {:first-name "Robert"
:last-name "Martin"
:age 71
:children ["Angela" "Micah" "Gina" "Justin"]}))
1. Test
Add a few more tests and make them pass.
––– spec/letsTest_spec.clj –––
(ns letsTest-spec
(:require [speclj.core :refer :all]
[letsTest :refer :all]))
(describe "letsTest"
(it "computes averages"
(should= 0 (average))
(should= 1 (average 1))
(should= 2 (average 1 3))))
––– src/letsTest.clj –––
(ns letsTest)
(defn average [& ns]
(let [n (count ns)]
(if (zero? n)
0
(/ (reduce + ns) n))))
1. Fireworks.
––– src/deps.edn –––
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
quil/quil {:mvn/version "4.3.1563"}}
:aliases {:fireworks {:main-opts [-m fireworks.main]}}}
––– src/fireworks/main.clj –––
(ns fireworks.main
(:require [quil.core :as q]
[quil.middleware :as m]
[fireworks.world :as world]))
(def frame-rate 30)
(def frame-ms (/ 1000 frame-rate))
(def max-frame-ms (* 2 frame-ms))
(defn setup []
(q/frame-rate frame-rate)
(q/color-mode :rgb)
(world/setup {})
(assoc (world/make)
::last-update-time (System/currentTimeMillis)
::mouse-state :mouse-up
::screen-size world/screen-size))
(defn mouse-clicked [world]
(let [[ox oy] world/screen-origin
mx (- (q/mouse-x) ox)
my (- (q/mouse-y) oy)]
(world/mouse-clicked [mx my] world)))
(defn get-ms-since-last-update [world]
(let [time (System/currentTimeMillis)
ms (- time (::last-update-time world))
ms (min max-frame-ms ms)
world (assoc world ::last-update-time time)]
[ms world]))
(defn check-for-mouse-click [world]
(let [mouse-current-state (if (q/mouse-pressed?)
:mouse-down :mouse-up)
mouse-old-state (::mouse-state world)
world (assoc world ::mouse-state mouse-current-state)
mouse-changed? (not= mouse-current-state mouse-old-state)
mouse-down? (= mouse-current-state :mouse-down)
mouse-clicked? (and mouse-changed? mouse-down?)]
(if mouse-clicked?
(mouse-clicked world)
world)))
(defn update-state [world]
(let [[ms world] (get-ms-since-last-update world)]
(->> world check-for-mouse-click (world/update-state ms))))
(defn draw-state [world]
(q/background 50)
(q/with-translation
world/screen-origin
(world/draw-state world)))
(defn -main [& _args]
(println "Fireworks has begun.")
(q/defsketch fireworks.main
:title "Fireworks"
:size world/screen-size
:setup setup
:update update-state
:draw draw-state
:features [:keep-on-top]
:middleware [m/fun-mode]))
––– src/fireworks/world.clj –––
(ns fireworks.world
(:require [fireworks.background :as background]
[fireworks.rocket :as rocket]
[fireworks.launcher :as launcher]))
(def screen-origin [0 0])
(def screen-size [1000 1000])
(defn setup [config]
)
(defn make [& config]
{:rockets []})
(defn update-state [ms world]
(->> world
(launcher/update-state ms)
(rocket/update-state ms)))
(defn draw-state [world]
(->> world
rocket/draw-state
background/draw-state))
(defn mouse-clicked [[x y] world]
world)
––– src/fireworks/background.clj –––
(ns fireworks.background
(:require
[quil.core :as q]))
(defn draw-ground [width]
(q/stroke [0 0 0])
(q/stroke-weight 5)
(q/line 0 0 width 0))
(defn draw-house [width _height]
(q/with-translation
[(/ width 2) -2]
(q/stroke [50 50 50])
(q/stroke-weight 2)
(let [house-width (/ width 15)
house-height (/ width -20)
roof-height (+ house-height (/ width -40))
door-height (/ house-height 2)
door-width (/ width 60)
door-x (/ (- house-width door-width) 2)
window-y (- (/ house-height 2) (/ width 140))
window-height (/ door-height 2)
window-width (abs window-height)
window-1-x (- door-x window-width)
window-2-x (+ door-x door-width)]
(q/fill 150 150 150)
(q/rect 0 0 house-width house-height)
(q/fill 100 100 100)
(q/triangle 0 house-height
house-width house-height
(/ house-width 2) roof-height)
(q/fill 130 130 130)
(q/rect door-x 0 door-width door-height)
(q/fill 200 200 0)
(q/rect window-1-x window-y window-width window-height)
(q/rect window-2-x window-y window-width window-height))))
(defn draw-state [world]
(let [[width height] (:fireworks.main/screen-size world)]
(q/with-translation
[0 (- height 5)]
(draw-ground width)
(draw-house width height))))
––– src/fireworks/launcher.clj –––
(ns fireworks.launcher
(:require [fireworks.rocket :as rocket]))
(defn update-state [_ms world]
(if (> (rand) 0.98)
(update world :rockets conj (rocket/make))
world))
––– src/fireworks/rocket.clj –––
(ns fireworks.rocket
(:require
[quil.core :as q]
[vector]))
(defn make []
(let [x (+ 50 (rand-int 900))
xs (if (> x 500) -1 1)
v [(* xs (rand 2)) (+ -12 (* -6 (rand)))]]
{:position [x 1000]
:velocity v
:duration (+ 1 (* 4 (rand)))
:levels (+ 1 (rand-int 2))}))
(defn update-rocket [ms rocket]
(let [secs (/ ms 1000)
wind 0
{:keys [position velocity duration]} rocket
new-duration (- duration secs)
new-position (vector/add position velocity)
new-velocity (vector/add velocity [(* secs wind) (* secs 5)])]
(assoc rocket :position new-position
:velocity new-velocity
:duration new-duration)))
(defn make-bomb [{:keys [levels] :as rocket}]
(if (zero? levels)
nil
(let [bomb (update rocket :levels dec)
dx (+ -5 (* 10 (rand)))
dy (+ -5 (* 10 (rand)))
bomb (update bomb :velocity vector/add [dx dy])]
(assoc bomb :duration 1.5))))
(defn explosion [rocket]
(let [n (+ 10 (rand-int 10))]
(remove nil? (for [_x (range n)] (make-bomb rocket)))))
(defn explode [rockets]
(loop [rockets rockets
new-rockets []]
(if (empty? rockets)
new-rockets
(let [{:keys [duration levels] :as rocket} (first rockets)]
(if (and (neg? duration)
(> (rand) 0.9))
(recur (rest rockets) (concat new-rockets (explosion rocket)))
(recur (rest rockets) (conj new-rockets rocket)))))))
(defn update-state [ms world]
(let [{:keys [rockets]} world
updated-rockets (map #(update-rocket ms %) rockets)
updated-rockets (explode updated-rockets)]
(doall (assoc world :rockets updated-rockets))))
(defn draw-state [world]
(let [{:keys [rockets]} world
[_width _height] (:fireworks.main/screen-size world)]
(q/fill [255 255 255])
(q/no-stroke)
(doseq [rocket rockets]
(let [[x y] (:position rocket)]
(q/ellipse x y 5 5))))
world)
––– src/vector.clj –––
(ns vector)
(defn add [[x1 y1] [x2 y2]]
[(+ x1 x2) (+ y1 y2)])
1. Celsius to Fahrenheit.
(defn ctof [celsius-s]
(let [celsius (Double/parseDouble celsius-s)
fahr (+ 32 (* 9/5 celsius))]
(str (h/html [:h1 "CTOF"] [:br] celsius-s "°C = " fahr "°F"))))
(defroutes app-routes
(GET "/ftoc/:fahr" [fahr] (ftoc fahr))
(GET "/ctof/:celsius" [celsius] (ctof celsius))
(route/not-found "Not Found"))
[1] The name “floating point” is historical, and refers to the way the computer represents the number internally. For now just consider a floating point number to be a possibly inexact decimal expansion of a fraction.
[2] A list is stored as a linked-list. A vector is stored as an array (more or less).
[3] It’s actually a different kind of list. But we’ll learn about that later.
[4] Don’t worry, you won’t notice it unless you do millions of calculations.
[5] Try computing 1000! and you’ll see just how big they can get.
[6] If you haven’t studied Taylor series before, you’ll find the exercise worthwhile and deeply satisfying.
[7] Position 763 to be precise.
[8] Which makes one wonder why we call them variables.
[9] System/currentTimeMillis returns the current time of day in milliseconds since midnight 1 Jan 1970.
[10] E Notation. 2.0X1030
[11] There is a lot to learn about printf. For the moment %s means string and %.0f means number with no decimals.
[12] H, S, and B are specified as integers between 0 and 255. Hue is simply the colors of the rainbow ROYGBV divided up into 256 levels. Saturation is the the richness of the color. 255 is full rich. 0 is grey. Brightness is the intensity of the color. 255 is full brightness. 0 is black. Don’t worry too much about this, we’re only going to modify the hue, we’ll let S and B be fully on.
[13] I used n here after trying a few other combination like 2n and n/2. But n seems to work best.
[14] You may have found that this function works well without the sort. You shouldn’t trust this. Sets don’t guarantee their order. It just so happens that the implementation of sets in the version of Clojure I’m currently using maintains sets of characters in alphabetical order. I’m quite sure that’s accidental and could change in a later version.