Here is my approach, which guarantees uniqueness and introduces some randomness.
- Use a sequence generator that is guaranteed to give a unique number . Since you are working with SQL Server, this may be the value of the
IDENTITY column. Otherwise, you can increase the application level value in C # code to achieve this. - Generate a random integer to lead to some randomness in the result. This can be done using
Random.Next() and any seed, even the number generated in the previous step. - Use the
EncodeInt32AsString method to convert integers from the previous two steps to two lines (one is a unique string, one is a random string). The method returns a string consisting of only allowed characters specified in the method. The logic of this method is similar to how a number is converted between different bases (for example, changing a valid string to only 0-9 or only 0-9A-F to get a decimal / sixth representation). Therefore, the result is a "number" consisting of "numbers" in the allowedList . - Combine the returned rows . Keep the entire unique string as it is (to guarantee uniqueness) and add as many characters from the random string to fill the total length to the desired length. If necessary, this concatenation can be a fantasy by entering characters from a random string at random points into a unique string.
Keeping the entire unique string, this guarantees the uniqueness of the final result. Using a random string, this introduces randomness. Accident cannot be guaranteed if the length of the target string is very close to the length of the unique string.
In my testing, calling EncodeInt32AsString for Int32.MaxValue returns a unique string with a length of 6 characters:
2147483647: ZIK0ZJ
Based on this, the length of the target line 12 will be ideal, although 10 is also reasonable.
EncodeInt32AsString Method
/// <summary> /// Encodes the 'input' parameter into a string of characters defined by the allowed list (0-9, AZ) /// </summary> /// <param name="input">Integer that is to be encoded as a string</param> /// <param name="maxLength">If zero, the string is returned as-is. If non-zero, the string is truncated to this length</param> /// <returns></returns> static String EncodeInt32AsString(Int32 input, Int32 maxLength = 0) { // List of characters allowed in the target string Char[] allowedList = new Char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; Int32 allowedSize = allowedList.Length; StringBuilder result = new StringBuilder(input.ToString().Length); Int32 moduloResult; while (input > 0) { moduloResult = input % allowedSize; input /= allowedSize; result.Insert(0, allowedList[moduloResult]); } if (maxLength > result.Length) { result.Insert(0, new String(allowedList[0], maxLength - result.Length)); } if (maxLength > 0) return result.ToString().Substring(0, maxLength); else return result.ToString(); }
GetRandomizedString Method
Now the previous method just takes care of the string encoding. To achieve the properties of uniqueness and randomness, you can use the following logic (or similar).
In the comments, Kevin pointed out the following risk with the implementation of the EncodeInt32AsString method:
The code must be modified so that it returns a string with a fixed length. Otherwise, you can never guarantee that the end result is unique. If this helps, draw one value generating ABCDE (Unique) + F8CV1 (Random) ... and then later, another value generating ABCDEF (unique) + 8CV1 (Random). Both values: ABCDEF8CV1
This is a very valid point, and it was covered in the next GetRandomizedString method, specifying the lengths for the unique and random strings. The EncodeInt32AsString method EncodeInt32AsString also been modified to subtract the return value by the specified length.
// Returns a string that is the encoded representation of the input number, and a random value static String GetRandomizedString(Int32 input) { Int32 uniqueLength = 6; // Length of the unique string (based on the input) Int32 randomLength = 4; // Length of the random string (based on the RNG) String uniqueString; String randomString; StringBuilder resultString = new StringBuilder(uniqueLength + randomLength); // This might not be the best way of seeding the RNG, so feel free to replace it with better alternatives. // Here, the seed is based on the ratio of the current time and the input number. The ratio is flipped // around (ie it is either M/N or N/M) to ensure an integer is returned. // Casting an expression with Ticks (Long) to Int32 results in truncation, which is fine since this is // only a seed for an RNG Random randomizer = new Random( (Int32)( DateTime.Now.Ticks + (DateTime.Now.Ticks > input ? DateTime.Now.Ticks / (input + 1) : input / DateTime.Now.Ticks) ) ); // Get a random number and encode it as a string, limit its length to 'randomLength' randomString = EncodeInt32AsString(randomizer.Next(1, Int32.MaxValue), randomLength); // Encode the input number and limit its length to 'uniqueLength' uniqueString = EncodeInt32AsString(input, uniqueLength); // For debugging/display purposes alone: show the 2 constituent parts resultString.AppendFormat("{0}\t {1}\t ", uniqueString, randomString); // Take successive characters from the unique and random strings and // alternate them in the output for (Int32 i = 0; i < Math.Min(uniqueLength, randomLength); i++) { resultString.AppendFormat("{0}{1}", uniqueString[i], randomString[i]); } resultString.Append((uniqueLength < randomLength ? randomString : uniqueString).Substring(Math.Min(uniqueLength, randomLength))); return resultString.ToString(); }
Output result
Calling the above method for a set of input values results in:
Input Int Unique String Random String Combined String ------------ ----------------- -------------- --------------------- -10 000000 CRJM 0C0R0J0M00 0 000000 33VT 03030V0T00 1 000001 DEQK 0D0E0Q0K01 2147 0001NN 6IU8 060I0U18NN 21474 000GKI VNOA 0V0N0OGAKI 214748 004LP8 REVP 0R0E4VLPP8 2147483 01A10B RPUM 0R1PAU1M0B 21474836 0CSA38 RNL5 0RCNSLA538 214748364 3JUSWC EP3U 3EJPU3SUWC 2147483647 ZIK0ZJ BM2X ZBIMK20XZJ 1 000001 QTAF 0Q0T0A0F01 2 000002 GTDT 0G0T0D0T02 3 000003 YMEA 0Y0M0E0A03 4 000004 P2EK 0P020E0K04 5 000005 17CT 01070C0T05 6 000006 WH12 0W0H010206 7 000007 SHP0 0S0H0P0007 8 000008 DDNM 0D0D0N0M08 9 000009 192O 0109020O09 10 00000A KOLD 0K0O0L0D0A 11 00000B YUIN 0Y0U0I0N0B 12 00000C D8IO 0D080I0O0C 13 00000D KGB7 0K0G0B070D 14 00000E HROI 0H0R0O0I0E 15 00000F AGBT 0A0G0B0T0F
As you can see above, a unique string is predictable for consecutive numbers, because it represents the same number represented in another database. However, a random string introduces some entropy so that users do not guess subsequent numbers. Moreover, alternating the “numbers” of a unique string and a random string, it becomes a bit more difficult for users to observe any pattern.
In the above example, the length of the unique string is set to 6 (since this allows it to represent Int32.MaxValue ), but the length of the random string is 4, because the OP requires a total length of 10 characters.