Saturday, October 1, 2011

Algorithm for incrementing alphabetical version numbers


Scenario

Forgive me if this post seems a little strange & horribly formatted (haven't quite worked out how to get everything to look right yet). This basically constitutes my first ever blog post .... ever! so I thought I might start off with a slightly light topic. I had an odd request a while back to implement a versioning algorithm that incrementally helped maintain a version number every time a form was submitted/re-submitted using Ms infopath 2010. Long story short, the product is a small enterprise system that integrates with a third party workflow engine, allowing users to conduct and maintain an internal quality and review process. However the client asked for the versioning to be alphabetically indexed to follow a sequence like so:

a, b, c, d,...., z, aa, ab, ac, ...., az, ba, bb, bc, ...........,zy, zz, aaa, aab, .......
 After much googling, I must admit I could not find any code on the web that I could easily reuse. I did not (and still don't) know if this form of sequencing has an official name. In the end, I simply gave in and came up with my own solution. I must admit it was not very hard to come up with one, but thought I might share it all the same, should someone else face a similar request.

Solution & Code

Before we get under way, I imported the following .Net C# namespaces:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;


I was told to hard code every new form to have a default start version value of 'a'. Regardless, in an effort to implement error checking and ensure that my code was always dealing with strings containing alphabetical characters. This is nothing special, just using the .net reg-ex library to check for unwanted chars in the version string. I wrote the following method:

        /// <summary>
        /// Ensure that all versioning strings have an alphabetical version
        /// </summary>
        private static bool IsCharacterString(string currentVersionNumber)
        {
            if (string.IsNullOrEmpty(currentVersionNumber))
                throw new ArgumentNullException("currentId");
            //match to see if there are any non alphabetical chars in the string
            var pattern = @"[^a-z]";
            var regx = new Regex(pattern);
            //if there are no such chars, then this is an accepted character versioned string
            return !regx.IsMatch(currentVersionNumber.ToLower());
        }
Next I proceeded to define the following constants (at class level as they are used in more than one function):

        const string DEFAULT_FIRST_CHAR_VERSION = "a";
        //ascii representations of characters a,z
        const int ASCII_A = 97;
        const int ASCII_Z = 122;

This was followed by the actual algorithm that produces the next version:


       /// <summary>
        /// algorithm to generate char Id's in the format: 'a,b,c...z,aa,ab,ac,ad.... az, ba.....zz, aaa, aab, ....'
        /// </summary>
        private static string NextCharVersion(string currentVersion)
        {
            if (string.IsNullOrEmpty(currentVersion))
                throw new ArgumentNullException("currentVersion");
            //ensure that the string is in lower case
            currentVersion = currentVersion.ToLower();
            //get a vector of chars
            var charList = Encoding.ASCII.GetBytes(currentVersion.ToCharArray());
            //check the last character in the list
            byte lastChar = charList.Last();
            //if the last char is [a-y] &  not Z
            if (lastChar >= ASCII_A
                && lastChar < ASCII_Z)
            {
                // simply increment the last character
                charList[charList.Length - 1]++;
            }
            else
            {
                //check if the version number string needs incrementing (only happens if all chars in string are now 'z')
                if (charList.All(c => c == ASCII_Z))
                {
                    //make a new list
                    var tmpList = charList.Select(c => (byte)ASCII_A).ToList();
                    //add the next version increment
                    tmpList.Add(ASCII_A);
                    charList = tmpList.ToArray();
                }
                else
                {
                    //reset the last char to 'a'
                    charList[charList.Count() - 1] = ASCII_A;
                    //iterate backwasrds & keep incrementing the previous chars (except the last) untill you hit a char that is in the range [a-y]
                    for (int i = charList.Count() - 2; i >= 0; i--)
                    {
                        if (charList[i] >= ASCII_A && charList[i] < ASCII_Z)
                        {
                            charList[i]++;
                            break;
                        }
                        else
                        {
                            charList[i] = ASCII_A;
                        }
                    }
                }
            }
            return new string(Encoding.ASCII.GetChars(charList));
        }

All this is then followed up with a public facing function which accepts a string representing the current version (part of the submitted form), checks if it is a valid string and progresses to determine the next version that should be set (note: actual code that sets the value in the submitted form has been omitted):


public static string UpdateNextCharVersion(string currentVersion)
        {
            //if string is null then return first char
            if (string.IsNullOrEmpty(currentVersion))
                return DEFAULT_FIRST_CHAR_VERSION;
            //if is characters then increment as per character version
            else if (IsCharacterString(currentVersion))
            {
                return NextCharVersion(currentVersion);
            }
            else
            {
                //return the version as is if the string cannot be versioned
                return currentVersion;
            }
        }

Hopefully this should save a lot of people an hour or two if they ever have to implement something like this.





No comments:

Post a Comment

Feel free to provide any feedback