I'm experimenting with using the Guid type in databases and applications but I don't like the string format of the Guid. It's not easily read or formatted on a report. I wanted to find a way to represent very large integers such as the Guid (a 128 bit integer under the covers), so I looked around and found a Base 36 type sample on Code Project article by Steve Barker that gave me a great start.
The problem with Base 36 is that several characters are similar to other characters or numbers, so took the code and modified it to use a limited set of 20 characters that are distinctive from numbers and other characters sufficiently to make it easy for humans to read them back or hand enter them in a user interface.
I downloaded the code and went to work making the modifications. Here's the core of the Base 30 struct:
//removed are chars similar to numbers or another char when printed: I, J, O, Q, V, Z private static List<char> alphaDigits = new List<char>(new char[]{ 'A','B','C','D','E','F','G','H','K','L','M','N','P','R','S','T','U','W','X','Y' }); private static byte Base30DigitToNumber(char Base30Digit) { if(char.IsDigit(Base30Digit)) { //Handles 0 - 9 return byte.Parse(Base30Digit.ToString()); } else { //Converts one base-30 digit to it's base-10 value if (alphaDigits.IndexOf(Base30Digit) > -1) { //Handles ABCDEFGHKLMNPRSTUWXY (these are letters that cannot be confused for numbers) int index = alphaDigits.IndexOf(Base30Digit) + 10; return (byte)(index); } else { throw new InvalidBase30DigitException(Base30Digit); } } } private static char NumberToBase30Digit(byte NumericValue) { //Converts a number to it's base-30 value. //Only works for numbers <= 29. if(NumericValue > 29) { throw new InvalidBase30DigitValueException(NumericValue); } //Numbers: if(NumericValue < 10) { return NumericValue.ToString()[0]; } else { //Note that A is code 65, and in this //scheme, A = 10, Y = 29 ABCDEFGHKLMNPRSTUWXY use alphaDigits for List<char> int index = NumericValue - 10; return alphaDigits[index]; //(char)(NumericValue + 55); } }
Now, I have added a couple of static methods to give me a shiny Base 30 guid string and convert that string back to a Guid here:
/// <summary> /// Convert a Guid to a set of four Base30 string values connected by a dash character. /// </summary> /// <param name="g"></param> /// <returns></returns> public static string GuidToBase30Set(Guid g) { byte[] b = g.ToByteArray(); Base30 b1 = BitConverter.ToUInt32(b, 0); Base30 b2 = BitConverter.ToUInt32(b, 4); Base30 b3 = BitConverter.ToUInt32(b, 8); Base30 b4 = BitConverter.ToUInt32(b, 12); return string.Format("{0}-{1}-{2}-{3}", b1, b2, b3, b4); } /// <summary> /// Convert the Base30 set string produced by GuidToBase30Set back to a Guid. /// </summary> /// <param name="s"></param> /// <returns></returns> public static Guid Base30SetToGuid(string s) { string[] p = s.Split('-'); if (p.Length != 4) throw new ArgumentException("Invalid Base30Set format."); try { Base30 b1 = p[0]; Base30 b2 = p[1]; Base30 b3 = p[2]; Base30 b4 = p[3]; uint x1 = (uint)b1.NumericValue; uint x2 = (uint)b2.NumericValue; uint x3 = (uint)b3.NumericValue; uint x4 = (uint)b4.NumericValue; byte[] a1 = BitConverter.GetBytes(x1); byte[] a2 = BitConverter.GetBytes(x2); byte[] a3 = BitConverter.GetBytes(x3); byte[] a4 = BitConverter.GetBytes(x4); byte[] gb = new byte[16]; a1.CopyTo(gb, 0); a2.CopyTo(gb, 4); a3.CopyTo(gb, 8); a4.CopyTo(gb, 12); return new Guid(gb); } catch { throw new ArgumentOutOfRangeException("Invalid Base30Set string."); } }
The code above gets you something like this:
Common Guid string: b908d243-c1ac-4ea4-a954-121e4ab5c334Base30Set from same: 47PGC95-1S8WCHP-MPTTA1-16CUN8P
You can download the code here (Base30.zip 3.6 KB).
Page rendered at Friday, September 03, 2010 4:05:34 AM (Mountain Daylight Time, UTC-06:00)
DisclaimerThe opinions expressed herein are just that, opinions. Don't have a fit if you think they're wrong. Post your comment or write your own blog.