Сгенерировать множество случайных алфавитно-цифровых строк

Вопрос или проблема

Сгенерировать множество случайных алфавитно-цифровых строк

Из внутренней части awk я хочу генерировать строку из X алфавитно-цифровых символов разумно случайным образом (т.е. случайным, но не криптографическим) по запросу и быстро.

В Ruby я мог бы сделать это:

ruby -e '
def rand_string(len, min=48, max=123, pattern=/[[:alnum:]]/)
    rtr=""
    while rtr.length<len do
        rtr+=(0..len).map { (min + rand(max-min)).chr }.
            select{|e| e[pattern] }.join
    end                     # завершает выполнение, когда достигнута минимальная длина 
    rtr[0..len]
end

(0..5).each{|_| puts rand_string(20)}'  

Выводит:

7Ntz5NF5juUL7tGmYQhsc
kaOzO1aIxkW5rmJ9CaKtD
49SpdFTibXR1WPWV7li6c
PT862YZQd0dOIaFOIY0d1
vYktRXkdsj38iH3s2WKI
3nQZ7cCVEXvoaOZvm6mTR

Для сравнения времени Ruby может создать 1,000,000 уникальных строк (без дубликатов) примерно за 9 секунд.

Учитывая это, я попробовал в awk:

awk -v r=$RANDOM '
# значение r будет новым сидом только при каждом вызове -- не при каждом вызове f
function rand_string(i) {
    s=""
    min=48
    max=123
    srand(r)
    while (length(s)<i) {
        c=sprintf("%c", int(min+rand()*(max-min+1)))
        if (c~/[[:alnum:]]/) s=s c
    }
    return s
}
BEGIN{ for (i=1; i<=5; i++) {print rand_string(20)}}'

Это не работает — один и тот же сид, один и тот же результат строки. Выводит:

D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI
D65CsI55zTsk5otzSoJI

Теперь попробуем считать /dev/urandom с помощью od:

awk '
function rand_string(i) {
    arg=i*4
    cmd="od -A n -t u1 -N " arg " /dev/urandom"  # это POSIX
    #             ^  ^                беззнаковый символ
    #                   ^  ^          количество байтов i*4
    s=""
    min=48
    max=123
    while (length(s)<i) {
        while((cmd | getline line)>0) {
            split(line, la)
            for (e in la) {
                if (la[e]<min || la[e]>max) continue
                c=sprintf("%c", la[e])
                if (c~/[[:alnum:]]/) s=s c
            }
        }
        close(cmd)
    }
    return substr(s,1,i)
}
BEGIN {for(i=1;i<=5;i++) print rand_string(20) }'

Это работает как задумано. Выводит:

sYY195x6fFQdYMrOn1OS
9mv7KwtgdUu2DgslQByo
LyVvVauEBZU2Ad6kVY9q
WFsJXvw8YWYmySIP87Nz
AMcZY2hKNzBhN1ByX7LW

Но теперь проблема в том, что пайп od -A n -t u1 -N " arg " /dev/urandom очень медленный — непригоден, за исключением тривиального количества строк.

Есть идеи, как я могу изменить один из этих awk так, чтобы он:

  1. Работал на большинстве платформ (т.е. стандартный POSIX комплект);
  2. Мог быстро производить разумно случайные строки длиной X.
  • Как я могу заменить строку случайной алфавитно-цифровой строкой длиной 48 символов, используя awk, где ответ – использовать внешние инструменты — слишком медленно;
  • Подменить данный шаблон случайным с помощью awk, но это случайное целое число и не использует srand;
  • Выполнить команду (для генерации случайных строк) внутри awk, но опять же использует оболочку (слишком медленно) и только для Linux.
  • Возьмите awk 1 и сделайте это:

    time awk -v r=$RANDOM '
    function rand_string(i) {
        s=""
        min=48
        max=123
        #srand(res) Дурной тон!! НЕПРАВИЛЬНО! Используйте srand только один раз, иначе будет сброс в ту же последовательность
        while (length(s)<i) {
            c=sprintf("%c", int(min+rand()*(max-min+1)))
            if (c~/[[:alnum:]]/) s=s c
        }
        return s
    }
    BEGIN{ 
        srand(r)      # Используйте srand ТОЛЬКО ОДИН РАЗ
        for (i=1; i<=1000000; i++) {print rand_string(20)}
    }'  | uniq -c | awk '$1>1'
    
    # Нет вывода, так что дубликатов нет
    real    0m9.813s
    user    0m10.413s
    sys 0m0.074s
    

    Сравните с (улучшенной) Ruby:

    time ruby -e '
    def rand_string(len, min=48, max=123, pattern=/[[:alnum:]]/)
        rtr=""
        while rtr.length<len do
            rtr+=(0..len).map { (min + rand(max-min)).chr }.
                select{|e| e.match?(pattern) }.join
        end                     # завершает выполнение, когда достигнута минимальная длина 
        rtr[0..len]
    end
    
    1_000_000.times{|_| puts rand_string(20)}' | uniq -c | awk '$1>1'
    
    # нет вывода, так что дубликатов нет
    
    real    0m8.901s
    user    0m9.544s
    sys 0m0.059s
    

    Увеличение времени для Ruby, вероятно, связано с остатком пайпа, который такой же для awk. Так что Ruby немного быстрее — как и ожидалось. (Но если вы используете gawk вместо awk, gawk завершает выполнение за 7.2 секунды.)

    Ответ или решение

    Существует несколько способов генерации случайных алфавитно-цифровых строк в AWK, который может быть исполнен на большинстве платформ. Ваша цель — быстро создать строки заданной длины, используя возможности стандартного POSIX AWK или GNU AWK (gawk). Ниже представлен окончательный ответ на вашу задачу.

    Решение

    Для получения случайных алфавитно-цифровых строк можно воспользоваться следующей реализацией. Важно использовать функцию srand() только один раз в начале программы, чтобы избежать генерации одних и тех же строк при многократных вызовах функции.

    awk '
    # Функция генерации случайной строки длиной i
    function rand_string(i) {
        s = ""
        min = 48       # минимальный ASCII код для цифр и некоторых символов
        max = 122      # максимальный ASCII код для букв, включая строчные буквы
    
        # генерируем символы до тех пор, пока длина строки не достигнет 'i'
        while (length(s) < i) {
            c = sprintf("%c", int(min + rand() * (max - min + 1)))
            # Проверяем, что символ является алфавитно-цифровым
            if (c ~ /[[:alnum:]]/) s = s c
        }
        return s
    }
    
    BEGIN { 
        srand()  # Инициализируем генератор случайных чисел
        for (i=1; i<=5; i++) {  # Генерируем 5 строк длиной 20 символов
            print rand_string(20)
        }
    }
    ' 

    Общее описание кода:

    1. Функция rand_string(i):

      • Эта функция генерирует строку, состоящую из случайных символов, пока длина строки не достигнет заданного значения i.
      • Внутри функции происходит выбор случайного символа с помощью rand(), преобразование его в символ с помощью sprintf(), и добавление в строку только в том случае, если символ подходит под регулярное выражение для алфавитно-цифровых символов.
    2. Генерация строк в блоке BEGIN:
      • Параметр srand() вызывается для инициализации генератора случайных чисел.
      • Цикл for используется для генерации и вывода на экран 5 случайных строк, каждая длиной 20 символов.

    Почему это решение эффективно

    • Скорость выполнения: Теперь функция rand_string() не обращается к внешним командам (например, od), что значительно увеличивает скорость генерации строк.
    • Совместимость: Данный код будет работать на большинстве платформ с установленным AWK (как стандартным, так и gawk).
    • Масштабируемость: Если вам нужно больше строк или другая длина, просто измените соответствующие параметры в цикле for.

    Заключение

    Этот пример демонстрирует, как можно использовать AWK для генерации случайных алфавитно-цифровых строк с необходимым уровнем производительности. В случае использования GNU AWK (gawk), вы также можете обратить внимание на дополнительные функции и возможности, которые могут улучшить процесс генерации.

    Оцените материал
    Добавить комментарий

    Капча загружается...