Случайная строка с использованием sed

#bash #sed

#bash #sed

Вопрос:

Я хочу выбрать случайную строку с sed помощью . Я знаю shuf -n и sort -R | head -n выполняю эту работу, но для shuf вас это необходимо установить coreutils , и для sort solution вас это не оптимально для больших данных :

Вот что я тестировал :

 echo "$var" | shuf -n1
 

Что дает оптимальное решение, но я боюсь за переносимость
, поэтому я хочу попробовать sed .

 `var="Hi
 i am a student
 learning scripts"`

output:
i am a student

output:
hi
 

Это должно быть Случайно.

Комментарии:

1. Добро пожаловать в SO. Stack Overflow — это страница вопросов и ответов для профессиональных и увлеченных программистов. Добавьте свой собственный код к вашему вопросу. Ожидается, что вы покажете, по крайней мере, объем исследований, которые вы провели для самостоятельного решения этого вопроса.

2. Спасибо, да, нет проблем, я отредактирую

3. Известно ли количество строк?

4. выполнение этого с помощью bash и sed было бы еще менее эффективным, что плохого в установке coreutils?

5. Почему вам нужно использовать именно sed? Практически в каждой системе доступно множество инструментов, которые могут подойти лучше.

Ответ №1:

Это во многом зависит от того, как вы хотите, чтобы выглядело ваше псевдослучайное распределение вероятностей. (Не пытайтесь использовать случайную, довольствуйтесь псевдослучайной. Если вам удастся сгенерировать действительно случайное значение, идите и получите свою нобелевскую премию.) Если вы просто хотите равномерное распределение (например, каждая строка имеет равную вероятность быть выбранной), то вам нужно априори знать, сколько строк в файле. Получить такое распределение не так просто, как позволить более ранним строкам в файле с немного большей вероятностью быть выбранными, и, поскольку это легко, мы это сделаем. Предполагая, что количество строк меньше 32769, вы можете просто сделать:

 N=$(wc -l < input-file)
sed -n -e $((RANDOM % N   1))p input-file
 

— редактировать —

Немного подумав об этом, я понимаю, что вам не нужно знать количество строк, поэтому вам не нужно читать данные дважды. Я не проводил тщательного анализа, но я считаю, что следующее дает равномерное распределение:

 awk 'BEGIN{srand()} rand() < 1/NR { out=$0 } END { print out }' input-file
 

— редактировать —
Эд Мортон предполагает в комментариях, что мы должны иметь возможность вызывать rand() только один раз. Кажется, что это должно сработать, но, похоже, этого не происходит. Любопытно:

 $ time for i in $(seq 400); do awk -v seed=$(( $(date  %s)   i)) 'BEGIN{srand(seed); r=rand()} r < 1/NR { out=$0 } END { print out}'  input; done | awk '{a[$0]  } END { for (i in a) print i, a[i]}' | sort
1 205
2 64
3 37
4 21
5 9
6 9
7 9
8 46

real    0m1.862s
user    0m0.689s
sys     0m0.907s
$ time for i in $(seq 400); do awk -v seed=$(( $(date  %s)   i)) 'BEGIN{srand(seed)} rand() < 1/NR { out=$0 } END { print out}'  input; done | awk '{a[$0]  } END { for (i in a) print i, a[i]}' | sort
1 55
2 60
3 37
4 50
5 57
6 45
7 50
8 46

real    0m1.924s
user    0m0.710s
sys     0m0.932s
 

Комментарии:

1. Мне нравится решение awk, и я согласен, кажется, что оно должно работать, но не могли бы вы просто вызвать rand() один раз в разделе BEGIN и использовать переменную вместо того, чтобы вызывать ее один раз на строку ввода? Поскольку srand() по умолчанию заполняется текущими секундами с момента значения эпохи, он выдаст тот же результат, если вы запустите его дважды в течение 1 секунды — если вам интересно, вы можете изменить это на awk -v seed="$RANDOM" 'BEGIN{srand(seed)...'

2. Последующие вызовы rand() будут выдавать новые значения… это только то же самое, если вы повторно создаете awk. Но вы правы! Нам нужно вызвать ее только один раз! Это немного ускорит процесс.

3. Это случай повторного появления awk, о котором я говорил. Маловероятно, что вы вызовете ее дважды за 1 секунду, но если вы это сделаете, вы получите тот же результат (если вам не повезет и вы не пересекли секунды с момента смены эпохи). Лично я обычно не хочу с этим справляться, но поскольку OP кажется довольно сфокусированным на случайности, я подумал, что предложу seed="$RANDOM" , если это имеет значение.

4. Пробовал вызывать rand только один раз в BEGIN, и вывод сильно смещен к началу файла. Не уверен, почему…

5. @EdMorton Я этого не делал. Запускаем скрипты для синхронизации.

Ответ №2:

 var="Hi
i am a student
learning scripts"

mapfile -t array <<< "$var"      # create array from $var

echo "${array[$RANDOM % (${#array} 1)]}"
echo "${array[$RANDOM % (${#array} 1)]}"
 

Вывод (например):

 learning scripts
i am a student
 

См.: help mapfile

Ответ №3:

Кажется, это лучшее решение для больших входных файлов:

 awk -v seed="$RANDOM" -v max="$(wc -l < file)" 'BEGIN{srand(seed); n=int(rand()*max) 1} NR==n{print; exit}' file
 

поскольку он использует стандартные инструменты UNIX, он не ограничен файлами длиной 32 769 строк или меньше, у него нет смещения в сторону любого конца ввода, он будет выдавать разные выходные данные, даже если вызывается дважды за 1 секунду, и он завершается сразу после печати целевой строки, а не продолжаетсядо конца ввода.


Обновить:

Сказав выше, у меня нет объяснения, почему скрипт, который вызывает rand() один раз в строке и считывает каждую строку ввода, примерно в два раза быстрее, чем скрипт, который вызывает rand() один раз и завершает работу в первой соответствующей строке:

 $ seq 100000 > file

$ time for i in $(seq 500); do
    awk -v seed="$RANDOM" -v max="$(wc -l < file)" 'BEGIN{srand(seed); n=int(rand()*max) 1} NR==n{print; exit}' file;
done > o3

real    1m0.712s
user    0m8.062s
sys     0m9.340s

$ time for i in $(seq 500); do
    awk -v seed="$RANDOM" 'BEGIN{srand(seed)} rand() < 1/NR{ out=$0 } END { print out}' file;
done > o4

real    0m29.950s
user    0m9.918s
sys     0m2.501s
 

Они оба выдали очень похожие типы выходных данных:

 $ awk '{a[$0]  } END { for (i in a) print i, a[i]}' o3 | awk '{sum =$2; max=(NR>1amp;amp;max>$2?max:$2); min=(NR>1amp;amp;min<$2?min:$2)} END{print NR, sum, min, max}'
498 500 1 2

$ awk '{a[$0]  } END { for (i in a) print i, a[i]}' o4 | awk '{sum =$2; max=(NR>1amp;amp;max>$2?max:$2); min=(NR>1amp;amp;min<$2?min:$2)} END{print NR, sum, min, max}'
490 500 1 3
 

Окончательное обновление:

Оказывается, это был вызов wc , который (неожиданно для меня, по крайней мере!) занимал большую часть времени. Вот улучшение, когда мы выводим его из цикла:

 $ time { max=$(wc -l < file); for i in $(seq 500); do awk -v seed="$RANDOM" -v max="$max" 'BEGIN{srand(seed); n=int(rand()*max) 1} NR==n{print; exit}' file; done } > o3

real    0m24.556s
user    0m5.044s
sys     0m1.565s
 

таким образом, решение, в котором мы вызываем wc заранее и rand() один раз, быстрее, чем вызов rand() каждой строки, как ожидалось.

Комментарии:

1. @WilliamPursell — не могли бы вы проверить вышеизложенное и попробовать тайминги, чтобы увидеть, получите ли вы похожие результаты?

2. На моем ноутбуке скрипт 2 run (запускающий wc -l) на самом деле быстрее. Только при одном запуске каждого из них — 12,864 в реальном времени против 24,625.

3. Но мне приходит в голову, что мы, возможно, упускаем из виду современное чудо огромной памяти! Для такого рода вещей в наши дни, вероятно, проще просто прочитать весь файл в память, а затем случайным образом выбрать строку в КОНЦЕ!

Ответ №4:

в оболочке bash сначала инициализируйте начальное значение в # line cube или по вашему выбору

 $ i=;while read a; do let i  ;done<<<$var; let RANDOM=i*i*i

$ let l=$RANDOM%$i 1 ;echo -e $var |sed -En "$l p"
 

если переместить ваши данные в varfile

 $ echo -e $var >varfile
$ i=;while read a; do let i  ;done<varfile; let RANDOM=i*i*i

$ let l=$RANDOM%$i 1 ;sed -En "$l p" varfile
 

поместите последний внутренний цикл, например for((c=0;c<9;c )) { ;}

Ответ №5:

С использованием GNU sed и bash ; нет wc или awk :

 f=input-file
sed -n $((RANDOM%($(sed = $f | sed '2~2d' | sed -n '$p'))   1))p $f
 

Примечание: три sed s в $(...) — неэффективный способ подделки wc -l < $f . Может быть, есть лучший способ — использовать только sed конечно.

Ответ №6:

Использование shuf :

 $ echo "$var" | shuf -n 1
 

Вывод:

 Hi