Генерировать случайную буквенно-цифровую строку в D

Я ищу независимый от платформы способ создания случайной буквенно-цифровой строки для одноразового номера Twitter. В настоящее время я использую это, но это зависит от /dev/random, который явно специфичен для UNIX:

private auto _getNonce() {
    ubyte[32] buffer;
    File devRandom  = File("/dev/random", "rb");
    auto randomData = devRandom.rawRead(buffer);
    auto randomBase = Base64.encode(randomData);

    auto nonWords   = regex(r"[\W_]", "g");
    return replaceAll(randomBase, nonWords, "");
}

Это соответствует рекомендации в документации по разработке Twitter для получения 32 байтов случайных данных, base-64 кодирует их и удаляет все небуквенно-цифровые символы.

Nonce не обязательно должен быть криптографически безопасным, он просто используется для предотвращения дублирования отправки. Любые предложения по лучшему способу сделать это, который работает как в Windows, так и в UNIX?


person Glitch Desire    schedule 23.11.2013    source источник
comment
Почему бы просто не использовать модуль std.random (dlang.org/phobos/std_random.html)? ?   -  person DejanLekic    schedule 23.11.2013
comment
@DejanLekic Ищете более полное решение, которое просто генерирует числа до ulong (на которые я могу рассчитывать только как на 32-битные). Мне нужна буквенно-цифровая строка, и все способы, которые я могу придумать, довольно хакерские.   -  person Glitch Desire    schedule 23.11.2013
comment
Вы можете делать с std.random именно то, что вы делаете в своем примере. Кодировка Base64 есть и в Фобосе...   -  person DejanLekic    schedule 23.11.2013
comment
@DejanLekic Я знаю, что кодировка Base64 есть в Фобосе, я использую ее в своем примере. :)   -  person Glitch Desire    schedule 23.11.2013


Ответы (2)


Примечание: после всех этих лет я должен отметить, что randomCover не повторяется. Это означает, что это решение будет предоставлять символ только один раз в нижнем регистре и один раз в верхнем регистре. Я посмотрел на использование cycle(), чтобы исправить это, но randomCover не поддерживает бесконечные диапазоны.

Если все, что вам нужно, это 32 буквенно-цифровых символа:

import std.algorithm : fill;
import std.ascii : letters, digits;
import std.conv : to;
import std.random : randomCover, rndGen;
import std.range : chain;
import std.stdio : writeln;

void main() {
    // dchar[] are random access, strings (char[]) are not
    auto asciiLetters = to!(dchar[])(letters);
    auto asciiDigits = to!(dchar[])(digits);

    dchar[32] key;
    fill(key[], randomCover(chain(asciiLetters, asciiDigits), rndGen));
    writeln(key);
}

В документации по диапазону конкретно указано

статическое утверждение (! isNarrowString! R); // узкие строки нельзя индексировать как диапазоны

узкие строки — это char[] и wchar[]. Хотя они являются массивами, нельзя найти «символ» 2531 в O (1), чтобы эти типы не считались произвольным доступом, что требуется для randomCover.

person he_the_great    schedule 24.11.2013
comment
Что значит char[] не произвольный доступ? Мне это кажется неправильным. string нельзя изменить, а char[] можно, если вы это имели в виду? - person Cubic; 02.12.2013
comment
Ах, вы говорили о символах Юникода. Да, точка принята. - person Cubic; 05.12.2013

std.random должен работать нормально. Он имеет множество генераторов случайных чисел, если вам важно, какой тип вы используете, но std.random.rndGen использует Mt19937, который является реализацией Мерсенн Твистер MT19937. Генераторы чисел std.random представляют собой диапазоны, благодаря чему они очень хорошо взаимодействуют с остальным Фобосом.

Одна из возможных реализаций

string genNonce()
{
    import std.algorithm, std.ascii, std.base64, std.conv, std.random, std.range;

    auto rndNums = rndGen().map!(a => cast(ubyte)a)().take(32);

    auto result = appender!string();
    Base64.encode(rndNums, result);

    return result.data.filter!isAlphaNum().to!string();
}

rndGen() генерирует бесконечный диапазон случайных чисел. map!(a => cast(ubyte)a)() затем лениво преобразует их в ubyte. И take(32) лениво берет первые 32 из них, так что результирующий диапазон имеет длину 32 вместо бесконечности. Затем Base64.encode используется для кодирования этого в выходном диапазоне, и это фильтруется так, чтобы оно содержало только буквенно-цифровые символы и преобразовывалось в string.

person Jonathan M Davis    schedule 23.11.2013
comment
Ага, именно это я и имел в виду, когда спрашивал Сухосина, почему он не использует std.random... :) Похоже, ему нужен был какой-то исходник... ;) Как всегда молодец, Джонатан! - person DejanLekic; 23.11.2013
comment
@DejanLekic Source всегда идеален, но было бы достаточно указателя на правильные функции. Я не считаю документацию Dlang самой простой вещью для подражания. :П - person Glitch Desire; 23.11.2013
comment
Разве это a => cast(ubyte)a не отбрасывает огромную часть сгенерированных случайных битов? Думаю, a => *cast(ubyte[a.sizeof]*)&a было бы лучше, но это некрасиво. И теперь у вас есть диапазон ubyte[4], и у phobos, похоже, нет функции выравнивания диапазона диапазонов. - person jpf; 24.11.2013
comment
@jpf Кроме того, зачем вам заботиться о том, какого размера было исходное число? Используемые вами 8 бит по-прежнему генерируются случайным образом. Нормально генерировать числа, намного большие, чем вы используете, и обычно нужно использовать %, чтобы уменьшить значение до нужного вам диапазона. Итак, вместо этого вы могли бы использовать cast(ubyte)(a % ubyte.max), но я не думаю, что это действительно чем-то отличается от простого приведения, если вас интересуют все числа, которые вписываются в ubyte. Но вы можете сделать что-то вроде того, что вы предлагаете, если вы действительно этого хотите. Ключевой частью ответа здесь действительно является использование std.random. - person Jonathan M Davis; 24.11.2013
comment
Ну, генерация большего количества случайных данных, чем необходимо, может повлиять на производительность. Но пока профилирование не показывает это как узкое место, это, вероятно, не имеет значения. - person jpf; 26.11.2013