Почему эта случайная строка Base36 не использует RandomNumberGenerator для случайного распределения символов - PullRequest
2 голосов
/ 14 мая 2019

Я пытаюсь сгенерировать случайную строку Base36, используя C#.Я использую 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();
}

Это работает, однако, глядя на результаты, становится очевидным, что строки неравномерно распределяют потенциальные символы.Есть много повторяющихся букв и очень редко появляются цифры.

Проблема в том, что вы берете первые символы случайной строки, или есть проблема с тем, как строится строка?

1 Ответ

2 голосов
/ 14 мая 2019

Я думаю, что вы должны использовать модуль вместо DivRem, если вы хотите придерживаться этого подхода.Моя мотивация для этого заключается в том, что если вы продолжите делить большое число на меньшее число, вы получите ситуацию, когда не имеет значения, было ли исходное число относительно большим или меньшим (т. Е. Величина 100 относительно большого числа).).

Например, возьмите эти числа в качестве входных данных (просто в качестве примера): 36.000.000 в качестве дивиденда и 10 в качестве делителя.Цикл while в вашей ToBase36String будет выглядеть следующим образом:

итерация 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 в любом случае из-за того, как работает целочисленное деление.

Суть, которую я пытаюсь подчеркнуть, заключается в том, что мне кажется ненужным случайное генерирование числабольше 36 для каждого символа base36, и ваш метод 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();
}
...