What's wrong?
Your code isn't easily extendable and there are a lot of repetitions. This means if you want to render numbers bigger then 9 you need to adjust multiple lines. Actually you need to rewrite the entire function. If you wanted to print the results to something else then the Console
- you need to change everything agian.
You cannot write tests for it because all calculations go directly to the console and you can validate them only visually.
How to fix it?
In order to optimize code you need to think like a mechanical engineer and ask yourself what parts will you need in your machine so that it does what you want it to do. Why? Because when you build a machine you cannot build a part that does everything - like your method does.
But if you see it as a machine then you can easier extract all the specialized parts for each task. It'll help you to think in OO-way so that you can extract those modules and apply SOLID principles.
Let's start...
- The diamond machine should oscillate between certain numbers, this means we need an oscillator
Part 1
- The diamond machine should generate lines of numbers, this means we need a diamond generator
Part 2
- The diamond machine should display the result, this means we need a display or rather a renderer in this case
Part 3
Here's an example how such parts could look like.
Part 1 - Oscillator
This part is responsible for creating collections of numbers oscillating between the given number.
interface IOscillator
{
IEnumerable<int> Oscilate(int between);
}
class Oscillator : IOscillator
{
public IEnumerable<int> Oscillate(int between)
{
var sign = 1;
for (var i = between; i <= between; i = i - 1 * sign)
{
yield return i;
if (i == 1) { sign = -1; }
}
}
}
Part 2 - Diamond generator
The diamond generator requires an oscillator to work. You specify one with dependency incjection by an abstraction layer IOscillator
. This part can use the oscillator to generate lines for the diamond. It also validate the size which has to be an odd number.
class Diamond : IEnumerable<DiamondLine>
{
private readonly int _size;
private readonly IOscillator _oscillator;
public Diamond(int size, IOscillator oscillator)
{
if (size % 2 == 0) { throw new ArgumentOutOfRangeException("Width must be odd"); }
_size = size;
_oscillator = oscillator;
}
private int Max => (_size + 1) / 2;
private IEnumerable<DiamondLine> GenerateDiamond()
{
foreach (var number in _oscillator.Oscillate(Max))
{
var currentMax = Max - number + 1;
var offset = Max - currentMax;
yield return new DiamondLine(offset, _oscillator.Oscillate(currentMax));
}
}
public IEnumerator<DiamondLine> GetEnumerator() => GenerateDiamond().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Each diamond line has specific properties. Those are encapsulated by the DiamondLine
. Our renderer will know what to do with it.
class DiamondLine
{
public DiamondLine(int offset, IEnumerable<int> numbers)
{
Offset = offset;
Numbers = numbers;
}
public int Offset { get; }
public IEnumerable<int> Numbers { get; }
}
Part 3 - Diamond Renderer
In the final part we will render each DiamondLine
as a string.
class DiamondRenderer
{
public IEnumerable<string> RenderDiamond(Diamond diamond)
{
foreach (var line in diamond)
{
yield return new string(' ', line.Offset) + string.Join(string.Empty, line.Numbers);
}
}
}
Usage
Now that you have so many parts, you can assemble them and print the lines to whatever you want.
var diamond = new Diamond(7, new Oscillator());
var diamondRenderer = new DiamondRenderer();
foreach (var line in diamondRenderer.RenderDiamond(diamond))
{
Console.WriteLine(line);
}
and this is the result:
I don't use a monotype font in LINQPad so the numbers are not perfectly alligned.
You might ask why so complicated? Becasue we want to separate all concerns. You have a working oscillator, a generator and a renderer.
If you now decide to extend it for numbers bigger then 9 you will only need to adjust the renderer so it renders numbers prefixed with a 0
like 03
.
Testability
Now each part can also be tested. Separately. You can write a better oscillator and test it or a renderer.
Possibilities
I've created a simple renderer but you could add an abstraction layer to it and use it via DI to implement the ToString
method of the Diamond
. You could then write various renderers that render simple strings or html or xml or whatever you want.
As long as you keep each functionality in a separate class you can easily extend it and make adjustments to any of them without affecting any other working parts.
Desclimer: I'm not a matematician so there might be a cooler way to write the oscillator.