简体   繁体   中英

Create a UNIQUE 5 character alphanumeric string

We are looking at creating promotional codes to send to customers, we have been told that each code sent HAS TO BE UNIQUE - 5 Characters - Alphanumeric.

I thought of doing a hash of a concatenated string and taking the first 5 characters of the hash, but there is a good chance that the same 5 characters will come up again and again.

Can anyone give me any pointers on creating this unique 5 character alpha numeric string that is unique EVERY TIME?

As I mentioned in the comments of my other answer, it may not be sufficient for your purposes. I worked up some more code which generates a string of random alpha-numeric characters. This time, they aren't limited to 0-9 and AF -- ie the hexadecimal equivalents of randomly-generated nibbles . Instead, they are comprised of the full range of alpha-numeric characters, at least with upper-case letters. This should sufficiently increase the potential for uniqueness given we're going from 16 possible characters with hex to 36 possible characters with the full alphabet and 0-9.

Still, when I ran it over 10,000,000 tries, there were plenty of dups. It's just the nature of the beast: the likelihood that you'll get dups with such a short string is fairly high. At any rate, here it is. You can play around with it. If your client doesn't mind lowercase letters -- eg if "RORYAP" is different from "RoryAp" -- then that would even further increase the likelihood of uniqueness.

/// <summary>
/// Instances of this class are used to geneate alpha-numeric strings.
/// </summary>
public sealed class AlphaNumericStringGenerator
{
    /// <summary>
    /// The synchronization lock.
    /// </summary>
    private object _lock = new object();

    /// <summary>
    /// The cryptographically-strong random number generator.
    /// </summary>
    private RNGCryptoServiceProvider _crypto = new RNGCryptoServiceProvider();

    /// <summary>
    /// Construct a new instance of this class.
    /// </summary>
    public AlphaNumericStringGenerator()
    {
        //Nothing to do here.
    }

    /// <summary>
    /// Return a string of the provided length comprised of only uppercase alpha-numeric characters each of which are
    /// selected randomly.
    /// </summary>
    /// <param name="ofLength">The length of the string which will be returned.</param>
    /// <returns>Return a string of the provided length comprised of only uppercase alpha-numeric characters each of which are
    /// selected randomly.</returns>
    public string GetRandomUppercaseAlphaNumericValue(int ofLength)
    {
        lock (_lock)
        {
            var builder = new StringBuilder();

            for (int i = 1; i <= ofLength; i++)
            {
                builder.Append(GetRandomUppercaseAphanumericCharacter());
            }

            return builder.ToString();
        }
    }

    /// <summary>
    /// Return a randomly-generated uppercase alpha-numeric character (A-Z or 0-9).
    /// </summary>
    /// <returns>Return a randomly-generated uppercase alpha-numeric character (A-Z or 0-9).</returns>
    private char GetRandomUppercaseAphanumericCharacter()
    {
            var possibleAlphaNumericValues =
                new char[]{'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','0','1','2','3','4','5','6','7','8','9'};

            return possibleAlphaNumericValues[GetRandomInteger(0, possibleAlphaNumericValues.Length - 1)];
    }

    /// <summary>
    /// Return a random integer between a lower bound and an upper bound.
    /// </summary>
    /// <param name="lowerBound">The lower-bound of the random integer that will be returned.</param>
    /// <param name="upperBound">The upper-bound of the random integer that will be returned.</param>
    /// <returns> Return a random integer between a lower bound and an upper bound.</returns>
    private int GetRandomInteger(int lowerBound, int upperBound)
    {
        uint scale = uint.MaxValue;

        // we never want the value to exceed the maximum for a uint, 
        // so loop this until something less than max is found.
        while (scale == uint.MaxValue)
        {
            byte[] fourBytes = new byte[4];
            _crypto.GetBytes(fourBytes); // Get four random bytes.
            scale = BitConverter.ToUInt32(fourBytes, 0); // Convert that into an uint.
        }

        var scaledPercentageOfMax = (scale / (double) uint.MaxValue); // get a value which is the percentage value where scale lies between a uint's min (0) and max value.
        var range = upperBound - lowerBound;
        var scaledRange = range * scaledPercentageOfMax; // scale the range based on the percentage value
        return (int) (lowerBound + scaledRange);
    }
}

I came up with this a while back which might serve what you're looking for.

/// <summary>
/// Return a string of random hexadecimal values which is 6 characters long and relatively unique.
/// </summary>
/// <returns></returns>
/// <remarks>In testing, result was unique for at least 10,000,000 values obtained in a loop.</remarks>
public static string GetShortID()
{
    var crypto = new System.Security.Cryptography.RNGCryptoServiceProvider();
    var bytes = new byte[5];
    crypto.GetBytes(bytes); // get an array of random bytes.      
    return BitConverter.ToString(bytes).Replace("-", string.Empty); // convert array to hex values.
}

I understand your requirement is that it "must" be unique, but remember, uniqueness is at best a relative concept. Even our old friend the GUID is not truly unique:

...the probability of the same number being generated randomly twice is negligible

If I recall correctly, I found my code wasn't 100% unique with 5 characters over many, many iterations (hundreds of thousands or possibly low-millions -- I don't recall exactly), but in testing with 6, the result was unique for at least 10,000,000 values obtained in a loop.

You can test it yourself at length 5 and determine if it's unique enough for your purposes. Just switch the 6 to a 5 if you want.

Addendum: Some of the others have reminded me that you might need to consider thread safety. Here's a modified approach:

private static object _lock = new object();

/// <summary>
/// Return a string of random hexadecimal values which is 6 characters long and relatively unique.
/// </summary>
/// <returns></returns>
/// <remarks>In testing, result was unique for at least 10,000,000 values obtained in a loop.</remarks>
public static string GetShortID()
{
    lock(_lock)
    {
        var crypto = new System.Security.Cryptography.RNGCryptoServiceProvider();
        var bytes = new byte[5];
        crypto.GetBytes(bytes); // get an array of random bytes.      
        return BitConverter.ToString(bytes).Replace("-", string.Empty); // convert array to hex values.
    }
}

I would create a table (PromoCode) with all possible 5 digit strings. Then create another table CampaignPromoCode with two fields:

Code varchar(5), CampaignId uniqueidentifier

This way you can keep track of used ones for each campaign. To get a random unused promocode use this statement:

select top 1 Code 
from PromoCode pc
    left join CampaignPromoCode cpc on cpc.Code = pc.Code
where cpc.CampaignId = 'campaign-id-goes-here'
  and cpc.Code is null
order by newid()

Bottom line here is that you should probably go back to management and tell them that the requirement to force 100% absolute unique is a very cost prohibitive request and that you can get a 99.9999% unique for a fraction of the cost. Then use roryap's code to generate a random, mostly unique code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM