【问题标题】:Why isn't this Base36 random string using RandomNumberGenerator randomly distributing the characters为什么这个 Base36 随机字符串不使用 RandomNumberGenerator 随机分布字符
【发布时间】:2019-05-14 10:57:41
【问题描述】:

我正在尝试使用C# 生成随机 Base36 字符串。我使用的是RandomNumberGenerator 而不是Random,因为代码需要是线程安全的。我有以下代码设置:

private readonly RandomNumberGenerator _random = RandomNumberGenerator.Create(); 

private string GenerateBase36Token(int length)
{
    string token = string.Empty;

    for (int i = 0; i < length; i++)
    {
        byte[] bytes = new byte[100];
        _random.GetBytes(bytes);
        token += ToBase36String(bytes)[0];
    }

    return token;
}

private string ToBase36String(byte[] toConvert)
{
    const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    BigInteger dividend = new BigInteger(toConvert);
    StringBuilder builder = new StringBuilder();

    while (dividend != 0)
    {
        BigInteger remainder;
        dividend = BigInteger.DivRem(dividend, alphabet.Length, out remainder);
        builder.Insert(0, alphabet[Math.Abs((int)remainder)]);
    }

    return builder.ToString();
}

这似乎有效,但是从结果来看,字符串显然没有均匀地分布潜在的字符。有很多重复的字母和数字很少出现。

问题是只取随机字符串的第一个字符还是字符串的构建方式有问题?

【问题讨论】:

  • 这里是我得到的分布:0,5163,5062,5148,53.138,143,11.154,137,143,1146,148,147,147,146,148,147,1160,148,14.147,114.145,142,141,139,145,14.14,147,1475,164,155,161,147,145,157,我从未得到过字母的“A”。事实证明,while 循环是问题的一部分。只做一次 DivRem 比做它直到你达到零要好。我的概率老师一直在问这个问题:“如果你有一副完全随机的牌,然后再洗牌一次。你得到的牌或多或少是随机的”?这是一个随机播放(while 循环)给出的随机性要小得多的情况。

标签: c# random base36


【解决方案1】:

如果您想坚持这种方法,我认为您应该使用模数而不是 DivRem。我这样做的动机是,如果你不断将一个大数除以一个较小的数,你会得到这样一种情况,即原始数字是相对更高还是更低(即相对于一个大数字而言,数量为 100 )。

例如,将这些数字作为输入(仅作为示例): 36.000.000 作为您的股息,以及 10 作为你的除数。 ToBase36String 中的 while 循环如下所示:

迭代 1: 股息:36.000.000 剩余:3.600.000

迭代 2: 股息:3.600.000 剩余:360.000

迭代 3: 股息:360.000 剩余:36.000

迭代 4: 股息:36.000 剩余:3.600

迭代 5: 股息:3.600 余数:360

迭代 6: 股息:360 余数:36

第 7 次迭代: 股息:36 余数:3

如果我们从 38.000.000 或 31.000.000 作为被除数开始,这并不重要,因为由于整数除法的工作原理,迭代 7 无论如何都会得到 3 的余数。

我想说的是,我似乎没有必要为每个 base36 字符随机生成一个大于 36 的数字,并且您的 GenerateBase36Token 方法为每个字符创建 100 个字节的数据。

另外,我想知道为什么您需要 base36 字符,而 base64 是一种广泛使用和接受的数据编码格式。

tl;dr:一个快速简单的解决方案可能是只生成一个随机数据字节,并使用模运算符而不是 DivRem 方法。

编辑:更新您的代码

private readonly RandomNumberGenerator _random = RandomNumberGenerator.Create(); 

private string GenerateBase36Token(int length)
{
    string token = string.Empty;

    for (int i = 0; i < length; i++)
    {
        byte[] bytes = new byte[1]; //edited byte array size
        _random.GetBytes(bytes);
        token += ToBase36String(bytes)[0];
    }

    return token;
}

private string ToBase36String(byte[] toConvert)
{
    const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    int dividend = (int)toConvert[0];
    StringBuilder builder = new StringBuilder();

    int remainder;
    remainder = dividend % alphabet.Length; //edited DivRem method usage to modulo operator usage
    builder.Insert(0, alphabet[remainder]);

    return builder.ToString();
}

【讨论】:

    猜你喜欢
    • 2022-10-19
    • 2013-01-02
    • 2014-10-08
    • 2014-03-26
    • 2017-05-20
    • 1970-01-01
    • 2020-05-13
    • 2020-11-22
    • 2017-06-11
    相关资源
    最近更新 更多