再談非重複隨機序列號生成算法

這段時間項目中又要開發兌換碼功能,此前的項目已經開發過,但是爲了保證這個功能在將來的可重複利用,我決定重構一下相關模塊。

原來的模塊不是我開發的,但也已經可以完成這個要求。但其中存在兩個問題,這也是基本上非重複隨機序列生成算法都要面對的問題
(1)是非重複性
(2)是效率

我們原來的程序員開發的,是使用的最低效的方式,即隨機生成後,遍歷已經生成的所有隨機序列號,如果重複則放棄這組隨機序列號,重新生成。在這個過程中,首先暴露的是低效,其次是隻能保證本批次的序列號不重複,再次啓動工具生成,兩個批次的隨機序列號就無法保證不重複了。

那麼也會有人問,爲什麼不使用GUID之類的算法,因爲兌換碼這個東西,其實有很多額外的需求,比如長度,隨機碼中使用的字符,有純數字的序列號,也有純字母的,所以諸如GUID,MD5,SHA等都不完全適合。

我前幾天曾經轉了一篇非重複隨機序列生成算法 ,其中作者的思路確實提供我很大的幫助,但是仔細看了作者的實現後,其實也發現了一點欠缺,該文中的算法解決了效率問題,也解決了重複問題,但是其能生成的序列號數量大大減少。打個比方,如果是生成長度10的序列號,文中的算法將保證[0~9]的數組只出現一次,按照排列組合的算法,最終出現的序列號總數是10*9*7*6*5*4*3*2*1,但實際我們需求的序列號,每一位的數字可以重複,但整個序列號不能產生重複,換句話說,我們的序列號總數應該是10的10次冪。

改進方案

(一)對於一個非重複的隨機序列號,要做到非重複,其N位上出現的字符的順序不能有相同的情況。使用排列組合的知識我們知道,假設我們有N位,每一位上允許的字符有m個的話,我們能產生的不重複的排列組合總量爲m1 m2 * m3 …*mN個,這就是非重複性
(二)隨機性,也就是我們從上述m的N次冪的序列中,隨機抽取K個,就能完成我們的目標。當然,不能用隨機數來決定抽取哪一個,這樣還得遍歷已經抽取出來的序列號,來判斷是否兩次挑選序列號是否挑到了同一個

(三)我的算法思路,首先求得一個排列組合的子集,即s1 * s2 * s3 *,,,*sN = C,C爲我們要生成的序列號總量,s是m的一個字符子集,s這個子集是從m集合中抽取的隨機的不重複的字符。這樣就能保證C的序列號是完全不會重複的序列號。s的子集,我使用了轉載文章中作者的算法,通過構建一個包含m個不同字符的數組,並對數組隨機抽取索引,進行移位交換,最後得到一個包含不重複字符的s子集

(四)序列號需求,對於任意長度的序列號,我們只需要增加N,對於序列號中出現的字符限制,我們只需要更改m集合中的字符內容,就可以得到純字符,或是純數字,或是混合的序列號

(五)不同批次的永不重複性,這個我選擇了使用增加前綴的方法,比如採用時間,來確定唯一性,然後將時間作爲序列號的一部分,加入前綴或是後綴,也可以自定義。
其實還有一種方法就是先利用本算法生成K個較短長度的非重複隨機序列號,K足夠大,保證這批序列號足夠使用,然後存入一個文件中,以後再生成時,將這K箇中隨機抽取一個,作爲批次的標識,生成一次,就拋棄K中的一個,就可以了

附上相應的只要算法代碼

if (count.ToString().Length > length)
{
    Console.WriteLine("生成數量大於位數,無法生成");
    return null;
}

var seed = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0);
var random = new Random((int)seed.TotalSeconds);
int[] bitTimes = new int[length];
int total_count = 1;
int idx = 0;
while (idx < length)
{
    int f = random.Next(0, 10);
    bitTimes[idx] = f;
    total_count *= f > 0 ? f : 1;
    idx++;
    if (total_count >= count)
    {
        break;
    }
}

while (total_count < count)
{
    for (int i = 0; i < length; i++)
    {
        if (bitTimes[i] < 10)
        {
            var c = bitTimes[i];
            bitTimes[i] += 1;
            total_count = total_count + total_count / c;
        }

        if (total_count >= count)
        {
            break;
        }
    }
}

var codes = new string[total_count];
for (int i = 0; i < length; i++)
{
    int coloum = bitTimes[i];
    coloum = Math.Max(1, coloum);
    int row = total_count / coloum;

    int[] num_array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int[] num = new int[coloum];
    int x = 0, tmp = 0, n = 0;
    for (int m = num_array.Length - 1; m > 0; m--)
    {
        x = random.Next(0, m + 1);
        tmp = num_array[m];
        num_array[m] = num_array[x];
        num_array[x] = tmp;
        num[n] = num_array[m];
        n++;
        if (n >= coloum)
        {
            break;
        }
    }

    for (int j = 0; j < row; j++)
    {
        for (int k = 0; k < coloum; k++)
        {
            var index = j * coloum + k;
            codes[index] = i == 0 ? num[k].ToString() : codes[index] + num[k];
        }
    }
}

return codes;

這裏只有實現,還未作優化,相關優化,代碼工程請移步
https://github.com/duotemplar/RedeemCodeGenerator

發佈了33 篇原創文章 · 獲贊 31 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章