Вопрос или проблема
Сгенерировать множество случайных алфавитно-цифровых строк
Из внутренней части 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 так, чтобы он:
- Работал на большинстве платформ (т.е. стандартный POSIX комплект);
- Мог быстро производить разумно случайные строки длиной X.
srand
;Возьмите 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)
}
}
'
Общее описание кода:
-
Функция
rand_string(i)
:- Эта функция генерирует строку, состоящую из случайных символов, пока длина строки не достигнет заданного значения
i
. - Внутри функции происходит выбор случайного символа с помощью
rand()
, преобразование его в символ с помощьюsprintf()
, и добавление в строку только в том случае, если символ подходит под регулярное выражение для алфавитно-цифровых символов.
- Эта функция генерирует строку, состоящую из случайных символов, пока длина строки не достигнет заданного значения
- Генерация строк в блоке
BEGIN
:- Параметр
srand()
вызывается для инициализации генератора случайных чисел. - Цикл
for
используется для генерации и вывода на экран 5 случайных строк, каждая длиной 20 символов.
- Параметр
Почему это решение эффективно
- Скорость выполнения: Теперь функция
rand_string()
не обращается к внешним командам (например,od
), что значительно увеличивает скорость генерации строк. - Совместимость: Данный код будет работать на большинстве платформ с установленным AWK (как стандартным, так и gawk).
- Масштабируемость: Если вам нужно больше строк или другая длина, просто измените соответствующие параметры в цикле
for
.
Заключение
Этот пример демонстрирует, как можно использовать AWK для генерации случайных алфавитно-цифровых строк с необходимым уровнем производительности. В случае использования GNU AWK (gawk), вы также можете обратить внимание на дополнительные функции и возможности, которые могут улучшить процесс генерации.