Text Diamond Kata

Here's a sample solution for the Text Diamond Kata that I'm using with cyber-dojo:

    Given a letter print a diamond starting with 'A'
    with the supplied letter at the widest point.
    For example: print-diamond 'E' prints


        A
       B B
      C   C
     D     D
    E       E
     D     D
      C   C
       B B
        A

This sample deliberately doesn't used recursion (the group is learning basic algorithm handling), and is written in C# using MsTest as the test framework.

Code

PaddedText.cs

using System.Text;

public class PaddedText
{
	public PaddedText()
	{
		this.SpaceCharacter = ' ';
	}

	public char SpaceCharacter { get; set; }

	public int ExternalSpacing { get; set; }

	public int InternalSpacing { get; set; }

	public string Text { get; set; }

	public override string ToString()
	{
		string externalSpaces = MakeStringOfLength(this.ExternalSpacing, this.SpaceCharacter);
		string internalSpaces = MakeStringOfLength(this.InternalSpacing, this.SpaceCharacter);

		string textFormat = this.InternalSpacing > 0
			? "{0}{1}{2}{1}{0}"
			: "{0}{1}{0}";

		return String.Format(textFormat, externalSpaces, this.Text, internalSpaces);
	}

	private static string MakeStringOfLength(int length, char repeatCharacter)
	{
		return length > 0 ? new String(repeatCharacter, length) : String.Empty;
	}
}

TextDiamond.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

public class TextDiamond
{
	private readonly string Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

	private char _highCharacter;

	public TextDiamond()
	{
		this.SpaceCharacter = 'X';
	}

	public char HighCharacter 
	{ 
		get 
		{ 
			return this._highCharacter; 
		} 
		set 
		{ 
			this._highCharacter = Char.ToUpper(value); 
		} 
	}

	public char SpaceCharacter { get; set; }

	public int Width
	{
		get
		{
			return PlaceInAlphabet(this.HighCharacter) * 2 + 1;
		}
	}

	public int PlaceInAlphabet(char letter)
	{
		Debug.Assert(Char.IsLetter(letter), "Invalid character");

		return this.Alphabet.IndexOf(Char.ToUpper(letter));
	}

	public override string ToString()
	{
		List<PaddedText> triangle = new List<PaddedText>();

		int from = PlaceInAlphabet(this.Alphabet.First());
		int to = PlaceInAlphabet(this.HighCharacter) + 1;

		int externalSpacing = (to > 1) ? to - 1 : 0;

		for (int i = from; i < to; ++i)
		{
			int lettersInRow = i == 0 ? 1 : 2;
			int internalSpacing = (i > 0) ? this.Width - lettersInRow - (externalSpacing * 2) : 0;
			triangle.Add(new PaddedText { Text = this.Alphabet.Substring(i, 1), ExternalSpacing = externalSpacing, InternalSpacing = internalSpacing });

			--externalSpacing;
		}

		return BuildDiamond(triangle);
	}

	public string BuildDiamond(IEnumerable<PaddedText> triangle)
	{
		StringBuilder builder = new StringBuilder();

		const string NewLine = "\r\n";

		builder.AppendLine(String.Join(NewLine, triangle));
		builder.AppendLine(String.Join(NewLine, triangle.Reverse().Skip(1)));

		return builder.ToString();
	}
}

Tests

PaddedTextTest.cs


using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class PaddedTextTest
{
	[TestMethod]
	public void PaddedText_No_Spacing_Returns_Text_Only()
	{
		var pt = new PaddedText
		{
			Text = "A"
		};

		Assert.AreEqual("A", pt.ToString());
	}

	[TestMethod]
	public void PaddedText_External_Spacing_Returns_Centered_Text()
	{
		var pt = new PaddedText
		{
			Text = "A",
			ExternalSpacing = 5
		};

		string output = pt.ToString();

		Assert.AreEqual(10, CountChars(output, pt.SpaceCharacter));
		Assert.AreEqual(1, CountChars(output, 'A'));
	}

	[TestMethod]
	public void PaddedText_Fully_Spaced_Returns_Repeated_Centered_Text()
	{
		var pt = new PaddedText
		{
			Text = "A",
			ExternalSpacing = 5,
			InternalSpacing = 2
		};

		string output = pt.ToString();

		Assert.AreEqual(12, CountChars(output, pt.SpaceCharacter));
		Assert.AreEqual(2, CountChars(output, 'A'));
	}


	public static int CountChars(string text, char toCount)
	{
		return text.Count(x => x.CompareTo(toCount) == 0);
	}
}

TextDiamondTest.cs

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TextDiamondTest
{
	[TestMethod]
	public void TextDiamond_Places_Are_Zero_Based()
	{
		var td = new TextDiamond();

		Assert.AreEqual(0, td.PlaceInAlphabet('A'));
		Assert.AreEqual(25, td.PlaceInAlphabet('Z'));
	}

	[TestMethod]
	public void TextDiamond_Letters_Are_Case_Insensitive()
	{
		var td = new TextDiamond();

		Assert.AreEqual(0, td.PlaceInAlphabet('a'));
	}

	[TestMethod]
	public void TextDiamond_BuildDiamond_Returns_Full_Diamond_From_Triangle()
	{
		const char SpaceChar = 'X';
		
		var list = new List<PaddedText>();

		list.Add(new PaddedText { ExternalSpacing = 2, InternalSpacing = 0, Text = "A", SpaceCharacter = SpaceChar });
		list.Add(new PaddedText { ExternalSpacing = 1, InternalSpacing = 1, Text = "B", SpaceCharacter = SpaceChar });
		list.Add(new PaddedText { ExternalSpacing = 0, InternalSpacing = 3, Text = "C", SpaceCharacter = SpaceChar });

		var td = new TextDiamond { SpaceCharacter = SpaceChar };

		string output = td.BuildDiamond(list);

		Assert.AreEqual(2, PaddedTextTest.CountChars(output, 'A'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'B'));
		Assert.AreEqual(2, PaddedTextTest.CountChars(output, 'C'));
		Assert.AreEqual(17, PaddedTextTest.CountChars(output, td.SpaceCharacter));
	}

	[TestMethod]
	public void TextDiamond_A_Returns_Single_String()
	{
		var td = new TextDiamond
		{
			HighCharacter = 'A',
			SpaceCharacter = ' '
		};

		string output = td.ToString();

		Assert.AreEqual(1, PaddedTextTest.CountChars(output, 'A'));
		Assert.AreEqual(0, PaddedTextTest.CountChars(output, 'B'));
		Assert.AreEqual(0, PaddedTextTest.CountChars(output, 'C'));
		Assert.AreEqual(0, PaddedTextTest.CountChars(output, td.SpaceCharacter));
		Assert.AreEqual(2, PaddedTextTest.CountChars(output, '\n'));
	}

	[TestMethod]
	public void TextDiamond_Is_Case_Insensitive()
	{
		var td = new TextDiamond
		{
			HighCharacter = 'a',
			SpaceCharacter = ' '
		};

		string output = td.ToString();

		Assert.AreEqual(1, PaddedTextTest.CountChars(output, 'A'));
	}

	[TestMethod]
	public void TextDiamond_B_Returns_ABBA_String()
	{
		var td = new TextDiamond
		{
			HighCharacter = 'B',
			SpaceCharacter = ' '
		};

		string output = td.ToString();

		Assert.AreEqual(2, PaddedTextTest.CountChars(output, 'A'));
		Assert.AreEqual(2, PaddedTextTest.CountChars(output, 'B'));
		Assert.AreEqual(0, PaddedTextTest.CountChars(output, 'C'));
		Assert.AreEqual(5, PaddedTextTest.CountChars(output, td.SpaceCharacter));
		Assert.AreEqual(3, PaddedTextTest.CountChars(output, '\n'));
	}

	[TestMethod]
	public void TextDiamond_C_Returns_ABCCBA_String()
	{
		var td = new TextDiamond
		{
			HighCharacter = 'C',
			SpaceCharacter = ' '
		};

		string output = td.ToString();

		Assert.AreEqual(2, PaddedTextTest.CountChars(output, 'A'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'B'));
		Assert.AreEqual(2, PaddedTextTest.CountChars(output, 'C'));
		Assert.AreEqual(17, PaddedTextTest.CountChars(output, td.SpaceCharacter));
		Assert.AreEqual(5, PaddedTextTest.CountChars(output, '\n'));
	}

	[TestMethod]
	public void TextDiamond_Z_Returns_Full_Diamond_String()
	{
		var td = new TextDiamond
		{
			HighCharacter = 'Z',
			SpaceCharacter = ' '
		};

		string output = td.ToString();

		Assert.AreEqual(2, PaddedTextTest.CountChars(output, 'A'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'B'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'C'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'D'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'E'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'F'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'G'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'H'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'I'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'J'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'K'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'L'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'M'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'N'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'O'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'P'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'Q'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'R'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'S'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'T'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'U'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'V'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'W'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'X'));
		Assert.AreEqual(4, PaddedTextTest.CountChars(output, 'Y'));
		Assert.AreEqual(2, PaddedTextTest.CountChars(output, 'Z'));

		Assert.AreEqual(2501, PaddedTextTest.CountChars(output, td.SpaceCharacter));
		Assert.AreEqual(51, PaddedTextTest.CountChars(output, '\n'));
	}
}