snippetcsharpMinor
Create a chain of unique integers from single integer
Viewed 0 times
uniquecreateintegerssinglefromintegerchain
Problem
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
obtained (1) to form a new number
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
Output
Example-2
Input
Output
Example-3
Input
Output
Solution:
```
using System.Collections.Generic;
using System.Linq;
public struct NumberChainItem
{
public int Number { get; set; }
public string GenerationDescription { get; set; }
}
public cla
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
123456789Output
Original number was 123456789
987654321 - 123456789 = 864197532
987654321 - 123456789 = 864197532
Chain length 2Example-2
Input
1234Output
Original number was 1234
4321 - 1234 = 3087
8730 - 378 = 8352
8532 - 2358 = 6174
7641 - 1467 = 6174
Chain length 4Example-3
Input
444Output
Original number was 444
444 - 444 = 0
0 - 0 = 0
Chain length 2Solution:
```
using System.Collections.Generic;
using System.Linq;
public struct NumberChainItem
{
public int Number { get; set; }
public string GenerationDescription { get; set; }
}
public cla
Solution
Yes I think your design is over-complicated. But more to the point, your implementation is wrong. The spec says
repeat these steps unless the new number has already appeared in the chain
But what you implement is "repeat these steps unless the new number is equal to the previous number". That's wrong.
Why did you get this wrong? You didn't create a method that clearly implements the specification, that's why.
When I see a problem like this I try to come up with a collection of methods where each exactly implements some part of the specification, in a general way. I would have implemented that line of the spec like this:
Easy method. Obviously correct. And when you use it in your solution, that solution will be seen to be obviously correct because it clearly and correctly implements an idea from the specification. Programming is about correctly solving small problems and combining those solutions together.
If you build up a small collection of helper methods like this you will very quickly discover that you can write the program you want to write so that it reads very close to its specification, and this gives you confidence that it is both correct and concise.
When I was solving the problem, a pluggable
It's not a bad idea to encapsulate that logic somewhere. But the question to ask when you're consider an interface is: do I need this generality? Am I really going to make two, three, a dozen different implementations of
Rather, it strikes me as the kind of thing that could be handled by a very short method:
There, done.
As another answer wisely pointed out: the mechanisms of OOP were designed for managing complexity when programming in the large, not for simple math. You just call
C# lets you treat arbitrary sequences as mathematical objects; take advantage of that to write concise code.
repeat these steps unless the new number has already appeared in the chain
But what you implement is "repeat these steps unless the new number is equal to the previous number". That's wrong.
Why did you get this wrong? You didn't create a method that clearly implements the specification, that's why.
When I see a problem like this I try to come up with a collection of methods where each exactly implements some part of the specification, in a general way. I would have implemented that line of the spec like this:
public static IEnumerable UntilDuplicate(
this IEnumerable items, IEqualityComparer equals)
{
var seen = new HashSet(equals);
foreach(var item in items)
{
if (seen.Contains(item))
yield break;
seen.Add(item);
yield return item;
}
}Easy method. Obviously correct. And when you use it in your solution, that solution will be seen to be obviously correct because it clearly and correctly implements an idea from the specification. Programming is about correctly solving small problems and combining those solutions together.
If you build up a small collection of helper methods like this you will very quickly discover that you can write the program you want to write so that it reads very close to its specification, and this gives you confidence that it is both correct and concise.
When I was solving the problem, a pluggable
INextNumberGenerator seemed like a good idea. Would you see this line of thinking as 'over-engineering'?It's not a bad idea to encapsulate that logic somewhere. But the question to ask when you're consider an interface is: do I need this generality? Am I really going to make two, three, a dozen different implementations of
INextNumberGenerator? If not, don't make an interface.Rather, it strikes me as the kind of thing that could be handled by a very short method:
static IEnumerable GenerateInfiniteSequence(
T seed,
Func next)
{
T current = seed;
while(true)
{
yield return current;
current = next(current);
}
}There, done.
As another answer wisely pointed out: the mechanisms of OOP were designed for managing complexity when programming in the large, not for simple math. You just call
Math.Cos. You don't create an instance of the Trigonometry class to get its ICosineProviderFactory.C# lets you treat arbitrary sequences as mathematical objects; take advantage of that to write concise code.
Code Snippets
public static IEnumerable<T> UntilDuplicate<T>(
this IEnumerable<T> items, IEqualityComparer<T> equals)
{
var seen = new HashSet<T>(equals);
foreach(var item in items)
{
if (seen.Contains(item))
yield break;
seen.Add(item);
yield return item;
}
}static IEnumerable<T> GenerateInfiniteSequence<T>(
T seed,
Func<T, T> next)
{
T current = seed;
while(true)
{
yield return current;
current = next(current);
}
}Context
StackExchange Code Review Q#139519, answer score: 8
Revisions (0)
No revisions yet.