The contents of this repository are to demonstrate a Python implementation of some sorting algorithms using TDD and the difference between favouring low or high priority transformations, as introduced by Uncle Bob in several mediums:
- Transformation Priority and Sorting
- Transformation Priority Premise
- Transformation Priority Premise, Ep. 1
- Transformation Priority Premise, Ep. 2
According to the Transformation Priority Premise refactorings have counterparts, transformations. These actually change the behaviour of our code. However if one uses these transformations according to their priority in the test driven development process, by striving for adding minimal complexity in each step, one would end up with simpler and more effective code.
If you browse through the commit history of this repository, you'll see a step by step TDD implementation of two list sorting algorithms: bubble and quicksort respectively. The extended commit messages explain the thought process and issues encountered in each step.
Each commit message from the TDD process looks like the following:
[<TDD PHASE>] <message> (<transformation>)
.
Where the TDD PHASE
could be:
RED
- meaning a new failing testcase, or part of the solution towards making the testcase pass, while the tests are still failingGREEN
- making a new testcase passREFACTOR
- change code while keeping the testcases passing, no change in behaviour
And the transformation refers to one of the following as introduced by Uncle Bob:
- Null
- Null to constant
- Constant to variable
- Add computation
- Split flow
- Variable to array
- Array to container
- If to while
- Recurse
- Iterate
- Assign
- Add case
To see the commits and their respective changes in order of creation, run after checking out the repository:
git log -p -w -M -M --reverse
To run the tests at any given step (commit), just do the following:
$ python test_sort.py
The end result of the second algorithm (quicksort) is flawed. If you were to
run it with a large (1000 or more elements) list that is ordered, you'd get a
RuntimeError
because of the exceeded maximum recursion depth.
This is due to all recursion happening on "one stack" - the divide and conquer
tree is degenerate and looks more like a linked list.
Example:
>>> from sort import quick_sort
>>> quick_sort(range(1000))
...
>>> RuntimeError: maximum recursion depth exceeded
Call stack of an ordered list (6 deep):
quick_sort([1, 2, 3, 4, 5])
├── quick_sort([])
└── quick_sort([2, 3, 4, 5])
├── quick_sort([])
└── quick_sort([3, 4, 5])
├── quick_sort([])
└── quick_sort([4, 5])
├── quick_sort([])
└── quick_sort([5])
├── quick_sort([])
└── quick_sort([])
Now compare it to the call stack of an unordered list of the same length (4 deep):
quick_sort([2, 4, 5, 3, 1])
├── quick_sort([1])
│ ├── quick_sort([])
│ └── quick_sort([])
└── quick_sort([4, 5, 3])
├── quick_sort([3])
│ ├── quick_sort([])
│ └── quick_sort([])
└── quick_sort([5])
├── quick_sort([])
└── quick_sort([])
A possible solution would be not to base partitioning of the list to lower and higher elements on the very first number (index 0) in the list. But rather choose the actual middle element. This way the list length would dramatically increase before we hit a recursion limit.