# Test first diamonds

Seb’s recent post Recycling tests in TDD introduced the “Print Diamond” kata. This has provoked a flurry of interesting posts looking at different approaches to solving it.

``````Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.

For example: print-diamond 'C' prints

A
B B
C   C
B B
A
``````

Thinking has been the theme that has dominated the posts others have published while trying out this kata. Alistair Cockburn began by suggesting that this kind of problem is ideal for thinking about the properties of the problem, and then deriving the code as an exercise - although his approach is to use tests to get him there. Ron Jeffries and George Dinwiddie both take an incremental TDD approach, although both are different.

Luckily I’d had a go at this kata before reading any of the other posts. I say lucky because I think having read all of the other posts I’d probably have done something else.

Ron and George both talk about thinking during their approach. I stepped through printing ‘A’, then 'B’ and 'C’ without really thinking about the algorithm at all. My first test looked like:

``````class TestPrintDiamond < MiniTest::Unit::TestCase
include PrintDiamond

def test_print_a
assert_equal 'A', print_diamond('A')
end
end
``````

I just took the easy way to make it pass:

``````module PrintDiamond
def print_diamond
'A'
end
end
``````

I’m not sure why I decided the interface would be a module that I mixed in, but I think that’s incidental.

My second and third tests followed a similar pattern, I’m going to put them both here because there’s nothing interesting about them.

``````def test_print_b
expected = [
' A ',
'B B',
' A ',
].join("\n")
assert_equal expected, print_diamond('B')
end

def test_print_c
expected = [
'  A  ',
' B B ',
'C   C',
' B B ',
'  A  ',
].join("\n")
assert_equal expected, print_diamond('C')
end
``````

I kept going with hard coding the answers, so the implementation for this was pretty simple too.

``````module PrintDiamond
def print_diamond(letter)
if letter == 'A'
[ 'A' ]
elsif letter == 'B'
[ ' A ', 'B B', ' A ' ]
else
[ '  A  ', ' B B ', 'C   C', ' B B ', '  A  ' ]
end.join("\n")
end
end
``````

So now I had tests that passed, but an implementation that wasn’t going to scale. This is the point that Seb mentions in his post that we can see the code is screaming for us to refactor. Seb points out that this is difficult, because there is so much going on at this point that it’s hard to know where to start.

Thanks to Sandi Metz I’ve been trying recently to make lots of very small refactorings. This is a great example of code that doesn’t quite look the same, but definitely has a pattern. Rather than going straight for trying to fix the problem, I wanted to expose the pattern. I want to extract the differences so I can more clearly see where the lines are the same.

I decided I wanted to focus on the “middle” of each row - the letters and the spacing between them.

I wrote a method that I thought would give me what I wanted:

``````ALPHABET = Array('A'..'Z')

def print_line(letter)
if letter == 'A'
letter
else
[letter, ' ' * ALPHABET.index(letter), letter].join
end
end
``````

I had a hunch that the spacing between the letters was the equivalent to the index of that letter in the alphabet. There’s a special case for 'A’ as it’s the only character that’s printed once, but I figured I would leave that for later.

I then tried using this method in a single place:

``````def print_diamond(letter)
if letter == 'A'
[print_line(letter)]
elsif letter == 'B'
[' A ', 'B B', ' A ']
else
['  A  ', ' B B ', 'C   C', ' B B ', '  A  ']
end.join("\n")
end
``````

So I extended using it for each of the central rows - the rows that had no extra spaces on the ends.

``````def print_diamond(letter)
if letter == 'A'
[print_line(letter)]
elsif letter == 'B'
[' A ', print_line(letter), ' A ']
else
['  A  ', ' B B ', print_line(letter), ' B B ', '  A  ']
end.join("\n")
end
``````

Oh a test failed! My hunch was wrong about the internal padding, or I’d miss-counted. 'C’ has 3 spaces between it, and yes the pattern is that the internal padding is always an odd number.

``````  def print_line(letter)
if letter == 'A'
letter
else
internal_padding = ALPHABET.index(letter) * 2 - 1
[letter, ' ' * internal_padding, letter].join
end
end
``````

Back to green. Now I was confident that this would work for all the other cases, so I pushed on and used it throughout, and renamed the method to pad_inside.

``````module PrintDiamond
ALPHABET = Array('A'..'Z')

def print_diamond(letter)
if letter == 'A'
elsif letter == 'B'
else
end.join("\n")
end

private
if letter == 'A'
letter
else
internal_padding = ALPHABET.index(letter) * 2 - 1
[letter, ' ' * internal_padding, letter].join
end
end
end
``````

The next thing to tackle is the external padding. Again I started by writing the code I thought might handle it:

``````def pad_out(letter, mid_letter, thing)
end
``````

Another hunch. The central row was never padded, and each other row the padding increased by one. My simple finger-counting maths seemed to think this would work. Let’s try it in one place and see how it goes:

``````def print_diamond(letter)
if letter == 'A'
elsif letter == 'B'
else
end.join("\n")
end
``````

Ok - something failed there… Ahh, I’m returning an array, not the string I need.

``````def pad_out(letter, mid_letter, thing)
end
``````

I can now work through using this method throughout:

``````def print_diamond(letter)
if letter == 'A'
elsif letter == 'B'
[
]
else
[
]
end.join("\n")
end
``````

Great - that works. I want to make a small refactoring to make all of the lines look the same. The central line that uses `letter` on each conditional is perhaps masking the pattern a bit.

``````def print_diamond(letter)
if letter == 'A'
elsif letter == 'B'
[
]
else
[
]
end.join("\n")
end
``````

So, now I want to remove the duplication inside one of the legs. I need some way of stepping through from `A` to the target letter, and then back to `A`.

``````def print_diamond(letter)
if letter == 'A'
elsif letter == 'B'
rows = Array('A'..letter) + Array('A'...letter).reverse

[
]
else
[
]
end.join("\n")
end
``````

There’s another hunch! It’s a bit tricky because it uses the difference between the ruby `..` range operator, and the `...` operator — using `..` means up to and including, and `...` means up to but not including. Let’s see if it works — I’ll just remove the old code for `B`.

``````def print_diamond(letter)
if letter == 'A'
elsif letter == 'B'
rows = Array('A'..letter) + Array('A'...letter).reverse
else
[
]
end.join("\n")
end
``````

Great, so I can try the same for the `C` branch.

``````def print_diamond(letter)
if letter == 'A'
elsif letter == 'B'
rows = Array('A'..letter) + Array('A'...letter).reverse
else
rows = Array('A'..letter) + Array('A'...letter).reverse
end.join("\n")
end
``````

Success! Time to remove the duplication and lose the `B` branch.

``````def print_diamond(letter)
if letter == 'A'
else
rows = Array('A'..letter) + Array('A'...letter).reverse
end.join("\n")
end
``````

Still works - so I’m confident it will work for the `A` branch too, so I’ll remove the conditional altogether.

``````def print_diamond(letter)
rows = Array('A'..letter) + Array('A'...letter).reverse
end
``````

I think this is a good point to talk about how this approach has differed from the others. Like some of the other solutions I didn’t do very up front thinking, but I didn’t really break the problem down. I just started to write code — I think Sandi Metz would call it shameless — and then looked for patterns.

One of Sandi’s key points from her OOD training is that it helps to look for simliar code, and extract the differences. This lets you make code that is nearly the same, actually be the same, and then you can tackle the duplication.

By this point I’d pretty much sketched out the algorithm. Its still not particularly nice, but it works and there’s something to work with.

``````module PrintDiamond
ALPHABET = Array('A'..'Z')

def print_diamond(letter)
rows = Array('A'..letter) + Array('A'...letter).reverse
end

private
if letter == 'A'
letter
else
internal_padding = ALPHABET.index(letter) * 2 - 1
[letter, ' ' * internal_padding, letter].join
end
end

end
end
``````

I’m concerned about that `if letter == 'A'`, and I definitely sense there’s an object or two waiting to leap out! I won’t bore you with the step-by-step refactorings, as this post is already pretty long. This was where I stopped though:

``````module PrintDiamond
ALPHABET = Array('A'..'Z')

def print_diamond(letter)
Diamond.new(letter).to_s
end

class Diamond

def initialize(letter)
@letter = letter
end

def to_s
rows.map { |row_letter| PaddedRow.new(row_letter, letter) }.join("\n")
end

def rows
top + bottom
end

def top
Array('A'..letter)
end

def bottom
Array('A'...letter).reverse
end
end

class Row

def initialize(letter)
@letter = letter
create_chars
end

def to_s
@chars.join
end

private
def row_size
# 0 -> 1, 1 -> 3, 3 -> 7, ...
ALPHABET.index(letter) * 2 + 1
end

def create_chars
@chars = Array.new(row_size) { ' ' }
@chars = letter
@chars[-1] = letter
end
end

def initialize(letter, diamond_letter)
@row = Row.new(letter)
@letter = letter
@diamond_letter = diamond_letter
end

def to_s
end

private