I've done a practice session on cyber-dojo and would like someone to review it.
Main question - is my solution too complex for such a simple task?
For example, I decided to split out printing and generating logic into ChainPrinter and NumberGenerator classes. This in my opinion conforms better with Single Responsibility and Open Closed Principles, but I can't get rid of a feeling that I've over complicated it.
Feel free to comment on other aspects of solution as well.
Instructions:
Given a number, we can form a number chain by
- arranging its digits in descending order
- arranging its digits in ascending order
- subtracting the number obtained in (2) from the number obtained (1) to form a new number
- and repeat these steps unless the new number has already appeared in the chain
Note that 0 is a permitted digit. The number of distinct numbers in the chain is the length of the chain. You are to write a program that reads numbers and outputs the number chain and the length of that chain for each number read.
Input and Output
The input consists of a positive number, less than 10^9. The output consists of the number chain generated by the input number, followed by its lengths exactly in the format indicated below.
Examples
Example-1
Input
123456789
Output
Original number was 123456789 987654321 - 123456789 = 864197532 987654321 - 123456789 = 864197532 Chain length 2
Example-2
Input
1234
Output
Original number was 1234 4321 - 1234 = 3087 8730 - 378 = 8352 8532 - 2358 = 6174 7641 - 1467 = 6174 Chain length 4
Example-3
Input
444
Output
Original number was 444 444 - 444 = 0 0 - 0 = 0 Chain length 2
Solution:
using System.Collections.Generic;
using System.Linq;
public struct NumberChainItem
{
public int Number { get; set; }
public string GenerationDescription { get; set; }
}
public class NumberChain : List<NumberChainItem>
{
public NumberChain(int startingNumber)
{
StartingNumber = startingNumber;
Items = new List<NumberChainItem>();
}
public int StartingNumber { get; private set; }
public List<NumberChainItem> Items { get; private set; }
}
public interface INextNumberGenerator
{
NumberChainItem GetNext(int input);
}
public class NumberGenerator : INextNumberGenerator
{
public NumberChainItem GetNext(int input)
{
var digits = input.ToString().Select(ch => int.Parse(ch.ToString())).ToArray();
var descending = DigitsToNumber(digits.OrderByDescending(i => i));
var ascending = DigitsToNumber(digits.OrderBy(i => i));
var number = descending - ascending;
var chainItem = new NumberChainItem
{
Number = number,
GenerationDescription = string.Format("{0} - {1}",
descending, ascending)
};
return chainItem;
}
private int DigitsToNumber(IEnumerable<int> digits)
{
var numberString = string.Join("", digits);
var number = int.Parse(numberString);
return number;
}
}
public class ChainGenerator
{
private readonly INextNumberGenerator _nextGenerator;
public ChainGenerator()
{
_nextGenerator = new NumberGenerator();
}
public ChainGenerator(INextNumberGenerator nextGenerator)
{
_nextGenerator = nextGenerator;
}
public NumberChain Run(int input)
{
var chain = new NumberChain(input);
int? previous = null;
var current = input;
while (current != previous)
{
var next = _nextGenerator.GetNext(current);
chain.Items.Add(next);
previous = current;
current = next.Number;
}
return chain;
}
}
public class ChainPrinter
{
public IEnumerable<string> Print(NumberChain chain)
{
yield return string.Format("Original number was {0}", chain.StartingNumber);
foreach (var item in chain.Items)
yield return string.Format("{0} = {1}",
item.GenerationDescription, item.Number);
yield return string.Format("Chain length {0}", chain.Items.Count());
}
}
Usage:
public void Instructions_Example1()
{
var input = 123456789;
var generator = new ChainGenerator();
var chain = generator.Run(input);
var printer = new ChainPrinter();
var result = printer.Print(chain);
}