Here’s another solution to the Secret Santa code kata, this time in my “native” programming language of C#. This is the second solution I’ve written this week to this problem. Code katas are about experimenting with different approaches to simple but not trivial problems.
My first solution, written in PowerShell, relied on selecting random pairs of people and using a “hill-climbing” algorithm to avoid getting stuck. This time, I gave myself the constraint that the solution had to be deterministic — no randomness. I had been toying with the idea of using an Abelian ring. A couple of common examples are the hours on a clock or the modulus (remainder) operator in math. But I couldn’t decide how I’d handle iterating through the members of that ring without duplicates. I determined that I’d need to re-sort the list.
I wrote this solution using test-driven development (TDD), meaning I wrote my tests before implementation — I won’t show all of those tests today. As it turned out, I didn’t need to code a Ring class. I find when I do TDD, I often don’t end up writing half the code I thought I’d need!
Unlike my PowerShell solution which just used strings, I knew I wanted to create a data object to store the incoming rows of data in, which I named the Participant class:
For a while, I thought I might need to implement
GetHashCode() in order to compare Participants, but again, TDD proved me wrong.
The plan for this approach was to:
1. Parse the incoming list
2. Resort the list
3. Display the results
The act of resorting took the majority of the development time. I created a
ListResorter class to do the work.
I started by writing tests….
These are the first two tests I wrote. I decided the simplest operation I needed to resort a list was the ability to swap two items in that list. Once I had that working, I picked the next piece, the ability to decide if two things can adjoin (“to be close to or in contact with”), and I proceeded from there.
Notice that the
LineResorter<T> constructor takes a list to operate against, and a function that compares two instances of type T and determines if they can be adjoining in the list. In the case of my unit tests, I used
(x,y) => x % 2 != y % 2, which is a fancy way of saying that two odds or two evens can’t be next to each other in the list. I wanted to use a different type (int) than I’d be using in my real use case to make sure I didn’t make any assumptions about the nature of type T. This comparison was the first one for two numbers that came to mind.
Each time I needed functionality out of the
ListResorter, I wrote a test. I watched it fail, then I made all the tests pass. If I saw opportunities to refactor, I took them whenever all my tests were green (passing). By the time I was done, I had about a dozen tests and this class:
This class has two public methods,
Resort(). That Abelian ring idea still lives in the
GetNextIndex() method, which says that the next member after the last in the list is the first, making the list circular.
Resort() does what you would expect.
The other methods in the class are marked internal, so that my TDD tests can access them via the
[Assembly:InternalsVisibleTo()] attribute in the
Assembly.cs code file. After design is done, I would consider rewriting the test methods that talk to internal methods so that they are exercised through the public method. I don’t want my unit tests to be break should someone decide to change the internal implementation of these methods. You can see a bit of this thinking in the
ThrowIfIndexesAreInvalid() method. I pulled this method out to avoid duplication of code during refactoring, where I already had unit tests in place and thus I didn’t need to write new ones.
Once I had
ListResorter working, writing a console wrapper was easy:
Most of the work done in the console app is formatting the output for display. The heavy lifting is done by the
This program outputs data like:
PS C:temp> .SantaKataApp.exe .names_santa.txt Luke Skywalker <firstname.lastname@example.org> gives a gift to Toula Portokalos <email@example.com> Toula Portokalos <firstname.lastname@example.org> gives a gift to Leia Skywalker <email@example.com> Leia Skywalker <firstname.lastname@example.org> gives a gift to Gus Portokalos <email@example.com> ...
I met my goal: the output is the same every time given the same input file.
I time-boxed the effort at two hours and I came close to reaching my limit. I initially began by writing an iterator, but abandoned it when I realized that I only wanted to traverse the collection once.
There is more that could be done to this program, of course. For example, I’m not happy about
GetNextIndex(). The only reason that
DisplayParticipants() needs a
ListResorter is to use its ring-like
GetNextIndex() method. This functionality should have been extracted into its own class.