Splitting a string into groups of up to 3 numbers, or one letter and up to 3 numbers There was an interesting idea brought up in The 2nd Monitor where one of our regulars was trying to split a bunch of strings into a specific format.

The format should be similar to the following:

A000
A00
900
90


Where A is any alphabetical letter, 0 is any number, and 9 is any number 1-9. No result string should begin with 0, it should always begin with A-Z or 1-9, and all alphabet characters will always be capitalized.

The input comes in a format similar to any of the following:

Input         | Result
900A000       | 900, A000
900900        | 900, 900
90            | 90
99099         | 990, 99
A009A09       | A009, A09
A009A09900    | A009, A09, 900
A0990A09900   | A09, 90, A09, 900
A099090A09900 | A09, 90, 90, A09, 900
A09A990       | A09, A990
A990          | A990


The input will always parse to a valid group of strings.

The method:

public List&lt;string&gt; SpecialSplit(string input)
{
    var result = new List&lt;string&gt;();

    var currentString = new StringBuilder(4);
    for (var i = 0; i &lt; input.Length; i++)
    {
        var c = input[i];

        if (currentString.Length &gt; 0)
        {
            // Determine whether we're at constraints or not.
            var firstCharLetter = currentString[0] &gt;= 'A' &amp;&amp; currentString[0] &lt;= 'Z';
            var atMaxLetterLength = firstCharLetter &amp;&amp; currentString.Length == 4;
            var atMaxNumberLength = !firstCharLetter &amp;&amp; currentString.Length == 3;

            // Split if at max letter/number length, or if we're on a letter.
            var mustSplit = atMaxLetterLength || atMaxNumberLength || c &gt;= 'A' &amp;&amp; c &lt;= 'Z';

            if (mustSplit)
            {
                // If we must split our string, then verify we're not leaving an orphaned '0'.
                if (c == '0')
                {
                    // Go back a letter, take it out of the new string, and set our `c` to it.
                    i--;
                    currentString.Length--;
                    c = input[i];
                }

                // Add and clear the string to our result.
                result.Add(currentString.ToString());
                currentString.Clear();
            }
        }

        // Add our `c` to the string.
        currentString.Append(c);
    }

    // Add our string to the result.
    result.Add(currentString.ToString());

    return result;
}


And the test code:

var tests = new string[] {
    "909A999",
    "909999",
    "90",
    "99099",
    "A009A09",
    "A009A09900",
    "A0990A09900",
    "A099090A09900",
    "A09A990",
    "A990"
};

var resultStrings = new List&lt;string&gt;();

foreach (var test in tests)
{
    var split = SpecialSplit(test);
    Console.WriteLine(test + ": {" + string.Join(", ", split) + "}");
    resultStrings.AddRange(split);
}

if (resultStrings.Any(x =&gt; x[0] == '0'))
    Console.WriteLine("Test failed");
else
    Console.WriteLine("Test passed");
right ... "tempted to solve this with a regex" doesn't quite cut it. There is a regex that kinda sorta somewhat works, but I didn't get it to do exactly what I want.
Doesn't matter though, because I can make it do what I wanted, namely spit out the results directly as separate groups. Instead I got it to match the "last result" an:

private readonly Regex someNiceName = new Regex(@"^((?:[A-Z]\d|[1-9])\d{1,2})+$", RegexOptions.Compiled);

public List&lt;string&gt; SpecialSplit(string input)
{
    if (input == "") 
    {
       return new List&lt;string&gt;(); // base case for recursion
    }
    var match = someNiceName.Match(input);
    string last = match.Groups[1].Value;
    var precedessors = SpecialSplit(input.Substring(0, match.Groups[1].Index));
    precedessors.Add(last);
    return precedessors;
}


With a bit of luck we don't actually need this mess, but can rely on Captures instead:

public List&lt;string&gt; SpecialSplit(string input) 
{
    var match = someNiceName.Match(input);
    var fullMatch = match.Groups[0];
    return fullMatch.Captures().Select(c =&gt; c.Value).ToList();
}


unfortunately I do not have C# available to check right now, but that might work better :)