**Number Games**
I've enjoyed working through chapter 4. It's simpler and more dense at the same time since it's all about numbers. Over the course of this chapter I (re)implemented several arithmetic basic functions such as `add1`, `sub1` – Those were rather helper functions – and moved on to implementing `pluss` and `minuss`, `multiply` and `divide` (I added the extra "s" to pluss and minuss to avoid redefining built-in functions).
The cool thing about this chapter is that I was able to implement these functions only in terms of very simple operations (add1, sub1) and recursion.
For example, `pluss` works as follows:
Given `n` and `m`, I want to evaluate their sum. For that to happen, I need to ask two questions:
- Is `m` equal to 0? If so, return `n`. This would make an addition such as 1 + 0 work straight away.
- Else, `add1` to the natural recursion of `pluss` with `n` and `(sub1 m)`. This counts `m` down with each recursion until it reaches 0.
If we were to add 1 and 3, the result of 4 would be calculated as:
```clojure
(pluss 1 3)
1 + (pluss 1 2)
1 + 1 + (pluss 1 1)
1 + 1 + 1 (pluss 1 0)
1 + 1 + 1 + 1
```
Pretty cool.
```clojure
(with-test
(def pluss
(fn [n m]
(cond (zero? m) n
:else (add1 (pluss n (sub1 m))))))
(is (= (pluss 0 0) 0))
(is (= (pluss 1 0) 1))
(is (= (pluss 0 1) 1))
(is (= (pluss 1 1) 2))
(is (= (pluss 1 2) 3))
(is (= (pluss 46 12) 58)))
```
For every function I write, I also make sure I start with the smallest possible test-case and then only add the code necessary to fix that test. I quiet enjoy it because it gives me confidence that, once I refactor, the functions still work.
For example, take `occur` which takes an atom `a` and a list `lat` as its arguments and returns the count of `a` in `lat`.
To build the simple algorithm, I start out writing the bare minimum setup for the first, smallest test:
```clojure
(with-test
(def occur
(fn [a lat]))
(is (= (occur 'a '()) 0)))
```
Running the test will fail since the function currently, and intentionally, only returns `nil` and not 0 when given `'a` and `'()` as its inputs.
I can fix this situation by writing the smallest possible solution which is to return 0, hardcoded:
```clojure
(with-test
(def occur
(fn [a lat] 0))
(is (= (occur 'a '()) 0)))
```
Our first test will now pass. We could, if we wanted to, make a commit at this point. On to the next.
```clojure
(with-test
(def occur
(fn [a lat] 0))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1)))
```
Running the tests will now fail for scenario 2 when we expect to receive 1 as the value and not 0. The smallest fix to solve for this is to introduce a conditional.
```clojure
(with-test
(def occur
(fn [a lat]
(cond (null? lat) 0
:else 1)))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1)))
```
Great! All tests are passing now. Now, on to the next, simplest test-case where `(occur 'a '(b))` should evaluate to 0.
```clojure
(with-test
(def occur
(fn [a lat]
(cond (null? lat) 0
:else 1)))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1))
(is (= (occur 'a '(b)) 0)))
```
The third test fails naturally. To fix it, I now have to introduce a new condition to check if `(car lat)` is equal to `a`:
```clojure
(with-test
(def occur
(fn [a lat]
(cond (null? lat) 0
(equan? (car lat) a) 1
:else 0)))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1))
(is (= (occur 'a '(b)) 0)))
```
Now, let's test for multiple occurrences of `a` in `lat`: `(is (= (occur 'a '(a a)) 2))`
```clojure
(with-test
(def occur
(fn [a lat]
(cond (null? lat) 0
(equan? (car lat) a) 1
:else 0)))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1))
(is (= (occur 'a '(b)) 0))
(is (= (occur 'a '(a a)) 2)))
```
The test will fail when run. Now we have to fix it by introducing adding 1 to the natural recursion of the function as follows: `(add1 (occur a (cdr lat)))`
```clojure
(with-test
(def occur
(fn [a lat]
(cond (null? lat) 0
(equan? (car lat) a) (add1 (occur a (cdr lat)))
:else 0)))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1))
(is (= (occur 'a '(b)) 0))
(is (= (occur 'a '(a a)) 2)))
```
Splendid, all tests are passing.
Now, I want to make sure that we get the right result, when the list is mixed, such as `(is (= (occur 'a '(a b c a)) 2))`
```clojure
(with-test
(def occur
(fn [a lat]
(cond (null? lat) 0
(equan? (car lat) a) (add1 (occur a (cdr lat)))
:else 0)))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1))
(is (= (occur 'a '(b)) 0))
(is (= (occur 'a '(a a)) 2))
(is (= (occur 'a '(a b c a)) 2)))
```
Adding the test makes it fail of course, since we're not accounting for this scenario. It's a simple fix, we need to allow our `:else` branch to continue searching for `a`s with `(occur a (cdr lat))`. The final solution to `occur` is
```clojure
(with-test
(def occur
(fn [a lat]
(cond (null? lat) 0
(equan? (car lat) a) (add1 (occur a (cdr lat)))
:else (occur a (cdr lat)))))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1))
(is (= (occur 'a '(b)) 0))
(is (= (occur 'a '(a a)) 2))
(is (= (occur 'a '(a b c a)) 2)))
```
I think this is pretty cool. We've got a first set of reasonable inputs and outputs covered. This would be a great time to run the full test-suite with `lein test`, and if everything passes, make a commit and push to master. For this I usually make myself a tiny script named `ship.sh` that runs the test-suite one final time and does the git push for me:
ship.sh
```bash
#!/bin/bash
lein test && git push origin master
```
Nothing fancy, but it keeps me from habitually doing a `git push origin master` without running all tests beforehand.
Anyways, I enjoyed showing you how I build my functions with TDD and baby-steps to arrive at a good place to commit and push. The full listing of my (un-refactored) chapter 4 is as follows:
```clojure
(ns the-little-clojurian.chapter4
(:require [clojure.test :refer :all]
[the-little-clojurian.chapter1 :refer :all]
[the-little-clojurian.chapter2 :refer :all]
[the-little-clojurian.chapter3 :refer :all]))
(with-test
(def add1
(fn [x] (+ x 1)))
(is (= (add1 0) 1))
(is (= (add1 67) 68))
(is (= (add1 68) 69)))
(with-test
(def sub1
(fn [x] (- x 1)))
(is (= (sub1 0) -1))
(is (= (sub1 1) 0))
(is (= (sub1 2) 1))
(is (= (sub1 3) 2)))
(with-test
(def pluss
(fn [n m]
(cond (zero? m) n
:else (add1 (pluss n (sub1 m))))))
(is (= (pluss 0 0) 0))
(is (= (pluss 1 0) 1))
(is (= (pluss 0 1) 1))
(is (= (pluss 1 1) 2))
(is (= (pluss 1 2) 3))
(is (= (pluss 46 12) 58)))
(with-test
(def minuss
(fn [n m]
(cond
(zero? m) n
:else (sub1 (minuss n (sub1 m))))))
(is (= (minuss 0 0) 0))
(is (= (minuss 1 1) 0))
(is (= (minuss 2 1) 1))
(is (= (minuss 3 2) 1))
(is (= (minuss 100 1) 99)))
(with-test
(def tup?
(fn [l]
(cond (null? l) true
(not (number? (car l))) false
:else (tup? (cdr l)))))
(is (= (tup? '()) true))
(is (= (tup? '(a)) false))
(is (= (tup? '(1)) true))
(is (= (tup? '(1 a)) false))
(is (= (tup? '(1 2)) true))
(is (= (tup? '(2 11 3 79 47 6))))
(is (= (tup? '(8 55 5 555))))
(is (= (tup? '(1 2 8 apple 4 3)) false))
(is (= (tup? '(3 (7 4) 13 9)) false)))
(with-test
(def addtup
(fn [tup]
(cond (null? tup) 0
:else (pluss (car tup) (addtup (cdr tup))))))
(is (= (addtup '()) 0))
(is (= (addtup '(1)) 1))
(is (= (addtup '(1 2)) 3))
(is (= (addtup '(1 2 3)) 6))
(is (= (addtup '(3 5 2 8)) 18))
(is (= (addtup '(15 6 7 12 3)) 43)))
(with-test
(def multiply
(fn [n m]
(cond (zero? m) 0
:else (pluss n (multiply n (sub1 m))))))
(is (= (multiply 0 0) 0))
(is (= (multiply 1 1) 1))
(is (= (multiply 1 0) 0))
(is (= (multiply 5 3) 15))
(is (= (multiply 13 4) 52)))
(with-test
(def tup+
(fn [tup1 tup2]
(cond (null? tup1) tup2
(null? tup2) tup1
:else (cons (pluss (car tup1) (car tup2))
(tup+
(cdr tup1) (cdr tup2))))))
(is (= (tup+ '() '()) '()))
(is (= (tup+ '(1) '(1)) '(2)))
(is (= (tup+ '(2 3) '(4 6)) '(6 9)))
(is (= (tup+ '(3 6 9 11 4) '(8 5 2 0 7)) '(11 11 11 11 11)))
(is (= (tup+ '(3 7) '(4 6 8 1)) '(7 13 8 1))))
(with-test
(def greater-than
(fn [n m] false
(cond (zero? n) false
(zero? m) true
:else (greater-than (sub1 n) (sub1 m)))))
(is (= (greater-than 0 0) false))
(is (= (greater-than 1 0) true))
(is (= (greater-than 12 133) false))
(is (= (greater-than 120 11) true))
(is (= (greater-than 4 6) false)))
(with-test
(def smaller-than
(fn [n m]
(cond (zero? m) false
(zero? n) true
:else (smaller-than (sub1 n) (sub1 m)))))
(is (= (smaller-than 0 0) false))
(is (= (smaller-than 0 1) true))
(is (= (smaller-than 4 6))))
(with-test
(def equal
(fn [n m]
(cond (smaller-than n m) false
(greater-than n m) false
:else true)))
(is (= (equal 0 0) true))
(is (= (equal 0 1) false))
(is (= (equal 1 1) true))
(is (= (equal 44 44) true)))
(with-test
(def expt
(fn [n m]
(cond (zero? m) 1
:else (multiply n (expt n (sub1 m))))))
(is (= (expt 1 1) 1))
(is (= (expt 2 2) 4))
(is (= (expt 2 3) 8))
(is (= (expt 5 3) 125)))
(with-test
(def divide
(fn [n m]
(cond (zero? m) (throw (IllegalArgumentException.))
(smaller-than n m) 0
:else (add1 (divide (minuss n m) m)))))
(is (= (divide 0 1) 0))
(is (thrown? IllegalArgumentException (divide 1 0)))
(is (= (divide 15 4) 3))
(is (= (divide 15 3) 5))
(is (= (divide 100 10) 10)))
(with-test
(def length
(fn [lat]
(cond (null? lat) 0
:else (add1 (length (cdr lat))))))
(is (= (length '()) 0))
(is (= (length '(hotdogs)) 1))
(is (= (length '(hotdogs with mustard sauerkraut and pickles)) 6))
(is (= (length '(ham and cheese on rye)) 5)))
(with-test
(def pick
(fn [n lat]
(cond (zero? n) nil
(one? n) (car lat)
:else (pick (sub1 n) (cdr lat)))))
(is (= (pick 0 '(apple)) nil))
(is (= (pick 1 '()) nil))
(is (= (pick 1 '(apple)) 'apple))
(is (= (pick 2 '(apple bananas)) 'bananas))
(is (= (pick 4 '(apple)) nil)))
(with-test
(def rempick
(fn [n lat]
(cond (zero? n) nil
(one? n) (cdr lat)
:else (cons (car lat)
(rempick (sub1 n)
(cdr lat))))))
(is (= (rempick 0 '()) nil))
(is (= (rempick 1 '(apple)) '()))
(is (= (rempick 2 '(apple bananas gin)) '(apple gin))))
(with-test
(def no-nums
(fn [lat]
(cond (null? lat) '()
(number? (car lat)) (no-nums (cdr lat))
:else (cons (car lat)
(no-nums
(cdr lat))))))
(is (= (no-nums '()) '()))
(is (= (no-nums '(1)) '()))
(is (= (no-nums '(apple)) '(apple)))
(is (= (no-nums '(5 pears 6 prunes 9 dates)) '(pears prunes dates))))
(with-test
(def all-nums
(fn [lat]
(cond (null? lat) '()
(number? (car lat)) (cons (car lat)
(all-nums (cdr lat)))
:else (all-nums (cdr lat)))))
(is (= (all-nums '()) '()))
(is (= (all-nums '(1)) '(1)))
(is (= (all-nums '(apple)) '()))
(is (= (all-nums '(1 apple 2 pears 3 bananas)) '(1 2 3))))
(with-test
(def equan?
(fn [a1 a2]
(cond (and (number? a1) (number? a2)) (equal a1 a2)
(or (number? a1) (number? a2)) false
:else (eq? a1 a2))))
(is (= (equan? 0 0) true))
(is (= (equan? 0 1) false))
(is (= (equan? 0 'a) false))
(is (= (equan? 'a 'a) true))
(is (= (equan? 'a 'b) false)))
(with-test
(def occur
(fn [a lat]
(cond (null? lat) 0
(equan? (car lat) a) (add1 (occur a (cdr lat)))
:else (occur a (cdr lat)))))
(is (= (occur 'a '()) 0))
(is (= (occur 'a '(a)) 1))
(is (= (occur 'a '(b)) 0))
(is (= (occur 'a '(a a)) 2))
(is (= (occur 'a '(a b c a)) 2))
(is (= (occur 'a '(b c d e)) 0)))
(with-test
(def one?
(fn [x] (equal x 1)))
(is (= (one? 0) false))
(is (= (one? 1) true))
(is (= (one? 2) false)))
```