Showing posts with label Sipping. Show all posts
Showing posts with label Sipping. Show all posts

Saturday, September 28, 2013

Writing code is like a mediation/negotiation

There seem to always be two sides of a story when writing code, and to some extent, the process of finding "the right" code, or the most elegant solution ends up proceeding as follows:

I just allow the code to essentially battle itself, with me as the programmer acting as the mediator in the negotiation.

So, the negotiation typically starts with the calling system asking attempting to call a function on the responding system that does not originally exist.  Consider this an "opening offer" where the calling system provides as much (or as little) detail as IT thinks is necessary for the responding system to answer the question or request.

This will generate a matching method in the responding system.  Now, we try to respond to the calling system's request, specifically from the perspective of the responding system.  Hopefully, the information provided is sufficient and an answer can simply be provided.

In many cases though - the responding system will need more information - so it should "request" this additional information by adding a parameter to it's function signature.  This will generate an error in the calling system's code.

By switching back to the calling system's perspective, providing the additional parameter is usually possible. But if it is not directly possible, either the calling system needs to go get the relevant information, or provide the next best option available.

By switching back to the responding system now, maybe the alternate information provided actually works, or may cause the responding system to change it's usage again.

This process continues until a  mutually agreeable set of code is found.

An important note about this process is that at the point that the original "opening offer"  was presented, a final solution to the problem was NOT completely understood.  This process is the method to FIND the solution.  It is NOT presumed that the solution is known at the beginning of this process.

Sunday, August 21, 2011

Sipping and Test Driven Development (TDD)

Sipping: Systemically Incremental Programming


Overview
Sipping and Test Driven Development  may initially sound like they are one and the same thing.  Indeed, they are closely related, and aim to solve many of the same underlying problems.  Additionally, while not mutually exclusive, they do compliment each other well, and using TDD within the Sipping framework will produce excellent results.

That being said, they are different.


First though, how is Sipping similar to TDD
Many of the benefits associated with TDD are also present when using the Systemically Incremental Programming.  The benefits section of TDD on wikipedia served as the basis for this list of benefits of sipping:

Sipping drives the design of a program. By focusing on a complex problem SIP by SIP, one must imagine how the functionality will be used by clients, and how all of the SIPs will fit together, creating one, coherent system. 
As described in Ordering your SIPs, the programmer is concerned with the interface before the implementation. This benefit is complementary to Design by Contract as it approaches code through test cases rather than through mathematical assertions or preconceptions.
Sipping offers the ability to take small steps when required. It allows a programmer to focus on the task at hand, as all other complexities and distractions have been pushed into other, subsequent SIPs. Exceptional cases and error handling are not considered initially, and SIPs to create and test these extraneous circumstances are implemented separately. 
Unlike TDD, simple Sipping does not typically require more code, while, I believe, still providing many of the benefits of TDD that make the implementation times for TDD shorter.[10] Large numbers of individual SIPs help to limit the number of defects in the code in the same way that large numbers of Tests under TDD do. The early and frequent nature of the testing helps to catch defects early in the development cycle, preventing them from becoming endemic and expensive problems. Eliminating defects early in the process usually avoids lengthy and tedious debugging later in the project.  TDD and Sipping share this benefit equally.
Sipping tends to lead to more modularized, flexible, and extensible code. This effect often comes about because the methodology requires that the developers think of the software in terms of small units that can be written and tested independently and integrated together later. This leads to smaller, more focused classes, looser coupling, and cleaner interfaces. The use of the mock object design pattern also contributes to the overall modularization of the code because this pattern requires that the code be written so that modules can be switched easily between mock versions for testing and "real" versions for deployment.
Because no more code is written than necessary to complete the SIP, it is generally easier for the developer to actually test all of this code, which he does as a matter of practice, before committing each SIP. 

How is Sipping different from TDD?

Specifically, I would categorize TDD as a subset of Sipping.  In other words, TDD does not specifically address how to break a problem apart.  Instead, it describes what to do once each "test" has been identified.  The "failing test" referred to in TDD would typically be the goal of a single SIP.

Where TDD seeks to describe how to implement one SIP, Systemically Incremental Programming describes the process of how to take complex problems, and pull them apart into separate SIPs in the first place.

TDD is also a relatively highly structured process, that relies on automated testing systems (Unit Testing for example).  Sipping is a more general set of guidelines that aims to solve a similar problem, but does not address directly how each "SIP" should be validated.  TDD (in many cases) will be an excellent choice for how to validate that the SIP has been completed to spec, but sipping works even if Testing is not the method of validation


Interestingly, going back to Wikipedia, virtually all of the "Vulnerabilities" listed for TDD do not apply to Sipping because most of the vulnerabilities of TDD are related to the overhead of initially creating and maintaining the TDD tests.  Sipping does not require this overhead.  Again, if used in conjunction with TDD, they may still be present - but they are absolutely not a prerequisite of Sipping.


The one vulnerability that both share (which was, however, not listed as a vulnerability of TDD) is that the incremental design process that both encourage, does not necessarily result in the most efficient overall system design.  Subsequent refactoring of the code may be required if performance is identified at any point during the testing process.  See 
Sipping, Code Efficiency and Preemptive Optimization for more information on this issue.

Conclusion
As you can see, Sipping TDD are closely related to one another, but not mutually exclusive.  The biggest difference is that in it's fundamental methodology design, Sipping does not require unit tests to be written for each SIP, but rather, this would be an optional extension to the implementation of an individual SIP.  This reduces the required overhead to starting using the Sipping method - as it can be applied to any project, any time, anywhere - giving SIP developers many of the advantages of TDD, without the test code overhead.

Friday, August 19, 2011

Sipping and the benefits of Decoupling

Sipping: Systemically Incremental Programming


What do I mean by "Decoupling"
From Wikipedia:

In software development, the term “decoupling” is used to identify the separation of software blocks that shouldn't depend on each other... Special design techniques allow software designers to have as few dependencies as possible. This typically reduces the risk of malfunction in one part of a system when the other part changed. It also forces the developer to focus on one thing at a time.
Decoupling lowers or minimizes Coupling.
Coupling is characterized as follows:
Coupling is usually contrasted with cohesion. Low coupling often correlates with high cohesion, and vice versa....[where] cohesion is a measure of how strongly-related the functionality expressed by the source code of a software module is.
In my humble opinion, the single most destructive force in software design is tightly coupled functionality.
In the wikipedia article on decoupling, it states that "special design techniques allow software designers to have as few dependancies as possible".  Sipping is just such a technique.  Specifically, I claimed that Sipping will "encourage" your code to be:

  • Simpler and more elegant, start to finish
  • More stable and bug free, out of the gates
  • Easier to read
  • Easier to change as conditions require
  • Easier to maintain by you (or others)
  • Better software at the end of the day for the end user

    and last, but not least
  • Much, much faster to produce in the first place


Code Efficiency
Before going any further, I want to point out that Sipping specifically (and consciously) does not always produce the most efficient code.  In other words, performance is not the primary objective of Sipping.  If performance is needed, it is quite possible that a more direct and optimized approach may be necessary.  I have found though, that in most cases, performance is not nearly as critical as readability, stability, extensibility, etc.
For further information on this, please see Sipping, Code Efficiency and Preemptive Optimization.

Unnecessary (or Inadvertent) Coupling
Let's take a simple example where, if developed as a unit, inadvertent coupling might occur, even if written by a "strong" developer.
I found this article on CSharp corner about Converting Numbers into Words:
http://www.c-sharpcorner.com/UploadFile/b942f9/6362/
The class that implements this is a console application, included here:

using System;
class Program
{
    static void Main()
    {
        string input;
        int number;
        bool isValid;
        bool isUK = false;
        Console.WriteLine("\nEnter '0' to quit the program at any time\n");
        while (true)
        {
            Console.Write("\nUse UK numbering y/n : ");
            input = Console.ReadLine();
            if (!(input.ToLower() == "y" || input.ToLower() == "n"))
                Console.WriteLine("\n  Must be 'y' or 'n', please try again\n");
            else
            {
                if (input.ToLower() == "y") isUK = true;
                Console.WriteLine("\n");
                break;
            }
        }
        do
        {
            Console.Write("Enter integer : ");
            input = Console.ReadLine();
            isValid = int.TryParse(input, out number);
            if (!isValid)
                Console.WriteLine("\n  Not an integer, please try again\n");
            else
                Console.WriteLine("\n  {0}\n", NumberToText(number, isUK));
        }
        while (!(isValid && number == 0));
        Console.WriteLine("\nProgram ended");
    }

    public static string NumberToText(int number, bool isUK)
    {
        if (number == 0) return "Zero";
        string and = isUK ? "and " : ""// deals with UK or US numbering
        if (number == -2147483648) return "Minus Two Billion One Hundred " + and +
        "Forty Seven Million Four Hundred " + and + "Eighty Three Thousand " +
        "Six Hundred " + and + "Forty Eight";
        int[] num = new int[4];
        int first = 0;
        int u, h, t;
        System.Text.StringBuilder sb = new System.Text.StringBuilder(); 
        if (number < 0)
        {
            sb.Append("Minus ");
            number = -number;
        }
        string[] words0 = {"""One ""Two ""Three ""Four ""Five ""Six ",
            "Seven ""Eight ""Nine "};
        string[] words1 = {"Ten ""Eleven ""Twelve ""Thirteen ""Fourteen ",
             "Fifteen ""Sixteen ""Seventeen ""Eighteen ""Nineteen "};
        string[] words2 = {"Twenty ""Thirty ""Forty ""Fifty ""Sixty ",
             "Seventy ""Eighty ""Ninety "};
        string[] words3 = { "Thousand ""Million ""Billion " };
        num[0] = number % 1000;           // units
        num[1] = number / 1000;
        num[2] = number / 1000000;
        num[1] = num[1] - 1000 * num[2];  // thousands
        num[3] = number / 1000000000;     // billions
        num[2] = num[2] - 1000 * num[3];  // millions
        for (int i = 3; i > 0; i--)
        {
            if (num[i] != 0)
            {
                first = i;
                break;
            }
        }
        for (int i = first; i >= 0; i--)
        {
            if (num[i] == 0) continue;
            u = num[i] % 10;              // ones
            t = num[i] / 10;
            h = num[i] / 100;             // hundreds
            t = t - 10 * h;               // tens
            if (h > 0) sb.Append(words0[h] + "Hundred ");
            if (u > 0 || t > 0)
            {
                if (h > 0 || i < first) sb.Append(and);
                if (t == 0)
                    sb.Append(words0[u]);
                else if (t == 1)
                    sb.Append(words1[u]);
                else
                    sb.Append(words2[t - 2] + words0[u]);
            }
            if (i != 0) sb.Append(words3[i - 1]);
        }
        return sb.ToString().TrimEnd();
    }
}

This class is about 100 lines of code.  It supports the following features:
  • Handles any Int32 number
  • Handles both positive and negative numbers
  • Both British and English Phrasing are supported
Some items that could be criticized about it are:
  • While this method totally works, and is efficient in it's design, I'm not exactly sure how it works.
  • The actual algorithm that constructs the words is somewhat cryptic, and not easily understood.
  • I think that most developers would struggle to write a method of this complexity.
  • In fact, many developers (myself included) would struggle to even understand how it works, let along come up with it ourselves
  • It would be harder still to write such a method without any bugs, and to be confident that the final design really does work as expected in all situations.
  • The entire process is "encapsulated" into one method.  This tightly couples all of the features supported into one process that must be (to some extent at least) developed, and understood as a unit.
  • I can't see an easy way to "evolve" this method.  In other words, again, because of it's underlying design, it appears that you'd have to understand the entire problem, and then write the entire method all at once.
  • If any bugs were found, most developers would have a hard time fixing them
  • The article states that the method could easily be extended to support bigger numbers and decimals, but exactly how this would be done is not immediately apparent to me.
Sipping and a Decoupled alternative
This problem would be challenging for most developers to write from scratch.  Using the process of sipping however, the problem can be broken down into a series of smaller steps, each of which is simple and easily understood.  In this way, we will take the relatively complex problem solved above, and simplify it.
In thinking about the underlying problem, I made the following observations:
  • The problem should be relatively easily "parsed" into SIPs
  • Each 3 digit sequence is identical.  eg. (365 is "Three Hundred Sixty Five", just as 365,000 is "Three Hundred Sixty Five [Thousand]", or Million, Billion, Etc)
  • This 3 digit sequence is the only thing that is "Modified" by British Phrasing.
  • Only the 1st digit needs to be looked at to determine if the number is negative.
In further thinking about the problem, I think that it can be divided into the following SIPs.
  1. Develop an abstract base class that can be used to parse a number into english words.
  2. Implement a "TensTranslator" that can handle the numbers between 0 and 99.
  3. Implement a "HundredsTranslator" that can handle any numbers from 0 to 999.
  4. Implement a "BigNumberTranslator" that can string together multiple HundredsTranslators to handle Thousands, Millions, Billions, Etc.
  5. Implement a NegativeNumberTranslator that can handle negative numbers.
  6. Modify the HundredsTranslator to handle US or British Phrasing.
By "evolving" the problem SIP by SIP as described above, we will get the same functionality, but hopefully with a more stable, easily understood and extensible algorithm.


Source Files


SIP 1: Abstract Base Class
Develop an abstract base class that can be used to parse a number into english words.
    1/// <summary>
    2/// This is the base class that all Translators will be based on
    3/// </summary>
    4public abstract class TranslatorBase
    5{
    6    public TranslatorBase(String input)
    7    {
    8        this.Input = input;
    9    }
   10
   11    public String Input { get; set; }
   12
   13    public abstract String Parse();
   14
   15    public override String ToString()
   16    {
   17        return this.Parse();
   18    }
   19}
All translating of numbers to words will be based on this simple abstract class.  In the 2nd SIP we will develop our first translator that will handle all numbers from 0 to 99.


SIP 2: Translating 0 to 99
This class will be used again and again and again by the rest of the system.

    1/// <summary>
    2/// This class will translate all numbers between 0 and 99 into words
    3/// </summary>
    4public class TensTranslator : TranslatorBase
    5{
    6    // Pass the result to the base class
    7    public TensTranslator(String input) : base(input) { }
    8
    9    // Ones (really 1-19) & Tens Arrays
   10    private String[] Ones = new String[] { "", "One", "Two", "Three", "Four", 
                   "Five", "Six", "Seven", "Eight", "Nine", "Ten", 
   11            "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", 
                   "Sixteen", "Seventeen", "Eighteen", "Nineteen" };
   12
   13    private String[] Tens = new String[] { "", "", "Twenty", "Thirty", "Fourty",
                  "Fifty", "Sixty", "Seventy", "Eighty", "Ninety" };
   14
   15    public override String Parse()
   16    {
   17        // Store the input as an int
   18        int inputAsInt = Int32.Parse(this.Input);
   19
   20        // If the input is not between 0 and 99, throw an exception
   21        if ((inputAsInt < 0) || (inputAsInt > 99)) 
                    throw new Exception("... can only handle numbers between 0 and 99");
   22
   23        // Translate the number into words.  
               // If the number is less than 20, we can use the ones array
   24        if (inputAsInt < 20) return this.Ones[inputAsInt];
   25
   26        // Otherwise, construct the number using the Tens and Ones array
   27        else
   28        {
   29          int ten = inputAsInt / 10;
   30          int one = inputAsInt % 10;
   31          return String.Format("{0} {1}", this.Tens[ten], this.Ones[one]).Trim();
   32        }
   33    }
   34}
The TensTranslator will handle any number between 0 and 99.  Notice how simple the class is.  This class will not handle Hundreds, Thousands, Millions, etc.  It will not even handle negative numbers.  But for all the numbers between 0 and 99, this class will handle it.
Writing this was easy.  It doesn't solve the entire problem, but it solves the first part of the problem.  We now have a translator that can translate any number under 100.  So far it's not nearly as functional as the original class, but was easy to develop, is easy to read, is totally stable, and will be easy to incorporate into additional SIPs to get the rest of the functionality that we want.
Using (and more importantly, testing) our new TensTranslator class is as simple as plugging it into the console app, similarly to how it was done in the original article:
    1using System;
    2using System.Text;
    3class Program
    4{
    5    static void Main()
    6    {
    7        Console.WriteLine("\nEnter '0' to quit the program at any time\n");
    8        do
    9        {
   10            Console.Write("Enter integer (0 to terminate): ");
   11            try
   12            {
   13                TranslatorBase translator = new TensTranslator(Console.ReadLine());
   14                Console.WriteLine("\n {0} is {1}\n", translator.Input, translator);
   15                if (translator.Input == "0") break;
   16            }
   17            catch (Exception ex)
   18            {
   19                Console.WriteLine("\n  ERROR: {0}\n", ex.Message);
   20            }
   21        }
   22        while (true);
   23        Console.WriteLine("\nProgram ended");
   24    }
   25}


SIP 3: HundredsTranslator - 0 to 999
Now that we've written and tested a 10's translator, we're ready to tackle bigger numbers.  The hundreds translator will use two 10's translators to handle any number up to 1000.

    1/// <summary>
    2/// Translate any 3 digit number between 0 and 999
    3/// </summary>
    4public class HundredsTranslator : TranslatorBase
    5{
    6    // Pad the input to ensure that this is always at least a 3 digit input string
    7    public HundredsTranslator(String input)
    8        : base(input.PadLeft(3, '0'))
    9    { }
   10
   11    public override String Parse()
   12    {
   13        // If the number is over 3 digits in length, we've got a problem
   14        if (this.Input.Length > 3) {
   15           throw new Exception("... can only handle numbers between 0 and 999");
   16        }
   17
   18        // Check if there's a hundreds column
   19        String hundredResult = String.Empty;
   20        if (this.Input.Substring(0, 1) != "0")
   21        {
   22            TensTranslator hundreds = new TensTranslator(this.Input.Substring(0, 1));
   23            hundredResult = String.Format("{0} Hundred", hundreds);
   24        }
   25
   26        // Parse the tens part of the input string
   27        TensTranslator tens = new TensTranslator(this.Input.Substring(1, 2));
   28        String tensResult = String.Format("{0}", tens);
   29
   30        // Return the english phrase
   31        return String.Format("{0} {1}", hundredResult, tensResult).Trim();
   32    }
   33}
The hundreds translator simply pads it's input to at least 3 digits.  In this way, 5 becomes 005, while 345 is left as 345.  
The algorithm then just checks if the first digit is anything other than 0.  If so, it creates a tens translator to parse it into One, Two, Three, etc and adds the word "Hundred" to it.  It then takes the remaining two digits and uses a TensTranslator to parse those into english as well.  It then returns the two strings (hundredResult and tensResult) as one, trimmed string.
We now have a Translator that can handle any number between 0 and 999.  We still can't handle Thousands, Millions, Billions, etc, negative numbers or British phrasing, but here again, it's simple, easily tested, easy to read and understand, and moves the problem further down the road.
In addition, because it uses the Tens translator, as we test this HundredsTranslator we are also further testing the TensTranslator.  If there are any issues in that first class, they will often be exposed during subsequent SIPs.  Now we just re-run the application, and should be able to enter any number up to one thousand.
Ready for SIP 4?


SIP 4: BigNumberTranslator - 0 through Quintillion.
Stringing together multiple HundredsTranslators, we can handle any number (of virtually any size)

    1/// <summary>
    2/// Handles numbers up to Quintillion
    3/// </summary>
    4public class BigNumberTranslator : TranslatorBase
    5{
    6    public BigNumberTranslator(String input)
    7        : base(input)
    8    { }
    9
   10    // Number group types
   11    private String[] NumberGroups = new String[] { "", "Thousand","Million","Billion", 
   12           "Trillion", "Quadrillion", "Quintillion" };
   13
   14    /// <summary>
   15    /// Parse the input string
   16    /// </summary>
   18    public override string Parse()
   19    {
   21        // Pad the left side of the string with 0's
   22        String input = this.Input.PadLeft(this.NumberGroups.Length * 3, '0');
   23
   24        // Iterate over and add each group of words to result, 3 digits at a time
   25        StringBuilder result = new StringBuilder();
   26        foreach (String groupName in this.NumberGroups)
   27        {
   28            // Remove the last 3 digits from the input string
   29            String group = input.Substring(input.Length - 3, 3);
   30            input = input.Substring(0, input.Length - 3);
   31
   32            // Add this group to the result
   33            HundredsTranslator hundredGroup = new HundredsTranslator(group);
   34
   35            // If this number has a name, insert it into the beginning of the result
   36            if (!String.IsNullOrEmpty(hundredGroup.ToString()))
   37            {
   38                // Insert a comma if there are already words
   39                if (result.Length > 0) result.Insert(0, ", ");
   40                result.Insert(0, String.Format("{0} {1}", group, groupName));
   41            }
   42        }
   43
   44        // Return the result
   45        return result.ToString();
   46    }
   47}
The BigNumberTranslator simply strings together HundredsTranslators, parsing each group of numbers, 3 at a time from the end of the input string passed to it.  Once again, we've made an incremental improvement to the overall system, building on our previous work.  This new class allows us to handle any number (up to 999 Quintillion) - and we could add support for larger numbers by simply adding additional group names to the end of the NumberGroups array.  How much easier could it be?
At this point, we still can't handle negative numbers, or British Phrasing.  Both of these are easily solved, each in their own SIPs of course!


SIP 5: Negative Numbers

    1/// <summary>
    2/// Adds the ability to handle negative numbers
    3/// </summary>
    4public class NegativeCapableTranslator : TranslatorBase
    5{
    6    public NegativeCapableTranslator(String input) : base(input) { }
    7
    8    public override string Parse()
    9    {
   10        // Check if the input starts with negative
   11        String input = this.Input;
   12        bool negative = input.StartsWith("-");
   13
   14        // Trim the negative
   15        if (negative) input = input.Substring(1);
   16
   17        // return the big number
   18        return String.Format("{0}{1}", (negative ? "Minus " : ""), 
                       new BigNumberTranslator(input));
   19    }
   20}
This NegativeNumberTranslator builds on the BigNumberTranslator developed in SIP 4 and simply adds the process of checking if the first character is - symbol, in which case it inserts the word "Minus" before the output of the BigNumberTranslator, which gets passed the rest of the input string (as a positive number).


SIP 6: British Phrasing
We can add british phrasing by simply modifying the HundredsTranslator.  If this translator had a static bool property for BritishPhrasing, we could simply insert the word "and" between the Hundred and Tens part - as follows.  Here again, you can see that this is simply a small, systemic change to the existing code that adds support for British Phrasing, but doesn't substantially change the rest of the system we've put in place.

    1/// <summary>
    2/// Translate any 3 digit number between 0 and 999
    3/// </summary>
    4public class HundredsTranslator : TranslatorBase
    5{
    6    // Pad the input to ensure that this is always at least a 3 digit input string
    7    public HundredsTranslator(String input)
    8        : base(input.PadLeft(3, '0'))
    9    { }
   10
   11    public static bool BritshPhrasing { get; set; }
   12
   13    public override String Parse()
   14    {
   15        // If the number is over 3 digits in length, we've got a problem
   16        if (this.Input.Length > 3) 
                 throw new Exception("... can only handle numbers between 0 and 999");
   17
   18        // Check if there's a hundreds column
   19        String hundredResult = String.Empty;
   20        if (this.Input.Substring(0, 1) != "0")
   21        {
   22            TensTranslator hundreds = new TensTranslator(this.Input.Substring(0, 1));
   23            hundredResult = String.Format("{0} Hundred", hundreds);
   24        }
   25
   26        // parse the tens part of the input string
   27        TensTranslator tens = new TensTranslator(this.Input.Substring(1, 2));
   28        String tensResult = String.Format("{0}", tens);
   29
   30        // Add british phrasing (if needed)
   31        if (!String.IsNullOrEmpty(hundredResult) && 
                     !String.IsNullOrEmpty(tensResult) && BritshPhrasing)
   32        {
   33            hundredResult += " and";
   34        }
   35
   36        // Return the english phrase
   37        return String.Format("{0} {1}", hundredResult, tensResult).Trim();
   38    }
   39}


Conclusion
Each time we added functionality, all we had to do to our console app was change the "new TensTranslator(...);" to "new HundredsTranslator(...);", and then to "new BigNumberTranslator(...);", etc.  After each SIP, we can fully test the new functionality, and check in our "improved" system.  And by the end of the 6th SIP, we've reproduced the original functionality in, as I see it, a simpler, more easily understood way.  In writing these classes, it took about 1 hour to write and test them - and once completed, I have a very high degree of confidence that they work as designed - and will continue to do so.  Further, if I want to add additional functionality, the framework designed should be easily extended and/or improved.
While in this case I ended up with more code than the original application (~200 lines, vs ~100 lines), this is actually the opposite of my common experience.  I find that usually I end up with less code (often substantially less code) when using the Sipping method.


The final code:

    1using System;
    2using System.Text;
    3class Program
    4{
    5    static void Main()
    6    {
    7        Console.WriteLine("\nEnter '0' to quit the program at any time\n");
    8        while (true)
    9        {
   10            Console.Write("\nUse UK numbering y/n : ");
   11            String input = String.Format("{0}", Console.ReadKey().KeyChar);
   12            if (!(input.ToLower() == "y" || input.ToLower() == "n"))
   13                Console.WriteLine("\n  Must be 'y' or 'n', please try again\n");
   14            else
   15            {
   16                if (input.ToLower() == "y") HundredsTranslator.BritshPhrasing = true;
   17                Console.WriteLine("\n");
   18                break;
   19            }
   20        }
   21        do
   22        {
   23            Console.Write("Enter integer (0 to terminate): ");
   24            try
   25            {
   26                TranslatorBase translator = 
                             new NegativeCapableTranslator(Console.ReadLine());
   27                Console.WriteLine("\n {0} is {1}\n", translator.Input, translator);
   28                if (translator.Input == "0") break;
   29            }
   30            catch (Exception ex)
   31            {
   32                Console.WriteLine("\n  ERROR: {0}\n", ex.Message);
   33            }
   34        }
   35        while (true);
   36        Console.WriteLine("\nProgram ended");
   37    }
   38
   39    /// <summary>
   40    /// This is the base class that all Translators will be based on
   41    /// </summary>
   42    public abstract class TranslatorBase
   43    {
   44        public TranslatorBase(String input)
   45        {
   46            this.Input = input;
   47        }
   48
   49        public String Input { get; set; }
   50        public abstract String Parse();
   51
   52        public override string ToString()
   53        {
   54            return String.Format("{0}", this.Parse());
   55        }
   56    }
   57
   58    /// <summary>
   59    /// This class will translate all numbers between 0 and 99 into words
   60    /// </summary>
   61    public class TensTranslator : TranslatorBase
   62    {
   63        // Pass the result to the base class
   64        public TensTranslator(String input) : base(input) { }
   65
   66        // Arrays
   67        private String[] Ones = new String[] { "", "One", "Two", "Three", "Four", 
                       "Five", "Six", "Seven", "Eight", "Nine", "Ten", 
   68                "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", 
                       "Sixteen", "Seventeen", "Eighteen", "Nineteen" };
   69
   70        private String[] Tens = new String[] { "", "", "Twenty", "Thirty", "Fourty", 
                       "Fifty", "Sixty", "Seventy", "Eighty", "Ninety" };
   71
   72        private int InputAsInt { get; set; }
   73
   74        public override String Parse()
   75        {
   76            // Store the input as an int
   77            this.InputAsInt = Int16.Parse(this.Input);
   78
   79            // If the input is not between 0 and 99, throw an exception
   80            if ((this.InputAsInt < 0) || (this.InputAsInt > 99)) 
                       throw new Exception("... can only handle numbers between 0 and 99");
   81
   82            // Translate the number into words.  
                   // If the number is less than 20, we can use the ones array
   83            if (this.InputAsInt < 20) return this.Ones[this.InputAsInt];
   84
   85            // Otherwise, construct the number using the Tens and Ones array
   86            else
   87            {
   88                int ten = this.InputAsInt / 10;
   89                int one = this.InputAsInt % 10;
   90                return String.Format("{0} {1}", this.Tens[ten], this.Ones[one]).Trim();
   91            }
   92        }
   93    }
   94
   95    /// <summary>
   96    /// Translate any 3 digit number between 0 and 999
   97    /// </summary>
   98    public class HundredsTranslator : TranslatorBase
   99    {
  100        // Pad the input to ensure that this is always at least a 3 digit input string
  101        public HundredsTranslator(String input)
  102            : base(input.PadLeft(3, '0'))
  103        { }
  104
  105        public static bool BritshPhrasing { get; set; }
  106
  107        public override String Parse()
  108        {
  109            // If the number is over 3 digits in length, we've got a problem
  110            if (this.Input.Length > 3) 
                       throw new Exception("... can only handle numbers between 0 and 999");
  111
  112            // Check if there's a hundreds column
  113            String hundredResult = String.Empty;
  114            if (this.Input.Substring(0, 1) != "0")
  115            {
  116               TensTranslator hundreds = new TensTranslator(this.Input.Substring(0,1));
  117                hundredResult = String.Format("{0} Hundred", hundreds);
  118            }
  119
  120            // parse the tens part of the input string
  121            TensTranslator tens = new TensTranslator(this.Input.Substring(1, 2));
  122            String tensResult = String.Format("{0}", tens);
  123
  124            // Add british phrasing
  125            if (!String.IsNullOrEmpty(hundredResult) && 
                        !String.IsNullOrEmpty(tensResult) && BritshPhrasing)
  126            {
  127                hundredResult += " and";
  128            }
  129
  130            // Return the english phrase
  131            return String.Format("{0} {1}", hundredResult, tensResult).Trim();
  132        }
  133    }
  134
  135    /// <summary>
  136    /// Handles numbers up to Quintillion
  137    /// </summary>
  138    public class BigNumberTranslator : TranslatorBase
  139    {
  140        public BigNumberTranslator(String input)
  141            : base(input)
  142        { }
  143
  144        // Number group types
  145        private String[] NumberGroups = new string[] { "", "Thousand", "Million", 
                  "Billion", "Trillion", "Quadrillion", "Quintillion" };
  146
  147        /// <summary>
  148        /// Parse the input string
  149        /// </summary>
  150        /// <returns></returns>
  151        public override string Parse()
  152        {
  153            // Parse the input until there is no input left
  154            StringBuilder result = new StringBuilder();
  155
  156            // Padd the left side of the string with 0's
  157            String input = this.Input.PadLeft(this.NumberGroups.Length * 3, '0');
  158
  159            // Iterate over each group and add the words to the list
  160            foreach (String groupName in this.NumberGroups)
  161            {
  162                // Remove the last 3 digits from the input string
  163                String group = input.Substring(input.Length - 3, 3);
  164                input = input.Substring(0, input.Length - 3);
  165
  166                // Add this group to the result
  167                HundredsTranslator hundred = new HundredsTranslator(group);
  168                group = String.Format("{0}", hundred);
  169
  170                // If this number has a name, insert it into beginning of the result
  171                if (!String.IsNullOrEmpty(group))
  172                {
  173                    if (result.Length > 0) result.Insert(0, ", ");
  174                    result.Insert(0, String.Format("{0} {1}", group, groupName));
  175                }
  176            }
  180
  181            // Return the result
  182            return result.ToString();
  183        }
  184    }
  185
  186    /// <summary>
  187    /// Adds the ability to handle negative numbres
  188    /// </summary>
  189    public class NegativeCapableTranslator : TranslatorBase
  190    {
  191        public NegativeCapableTranslator(String input) : base(input) { }
  192
  193        public override string Parse()
  194        {
  195            // Check if the input starts with negative
  196            String input = this.Input;
  197            bool negative = input.StartsWith("-");
  198
  199            // Trim the negative
  200            if (negative) input = input.Substring(1);
  201
  202            // return the big number
  203            return String.Format("{0}{1}", (negative ? "Minus " : ""), 
                        new BigNumberTranslator(input));
  204        }
  205    }
  206}