Случайная строка с использованием sed - PullRequest
2 голосов
/ 19 апреля 2019

Я хочу выбрать случайную строку с помощью 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

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

Ответы [ 6 ]

3 голосов
/ 19 апреля 2019

Это сильно зависит от того, как вы хотите, чтобы ваше псевдослучайное распределение вероятностей выглядело.(Не пытайтесь случайным образом, будьте довольны псевдослучайным. Если вам удастся сгенерировать действительно случайное значение, соберите свой нобелевский приз.) Если вы просто хотите равномерного распределения (например, каждая строка имеет равную вероятность бытьвыбран), то вам нужно знать априори, сколько строк в файле.Получить такой дистрибутив не так просто, как позволить немного более раннему выбору более ранних строк в файле, и так как это легко, мы сделаем это.Предполагая, что количество строк меньше 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
2 голосов
/ 19 апреля 2019
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

1 голос
/ 20 апреля 2019

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

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>1&&max>$2?max:$2); min=(NR>1&&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>1&&max>$2?max:$2); min=(NR>1&&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() для каждой строки, как и ожидалось.

0 голосов
/ 03 мая 2019

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

$ echo "$var" | shuf -n 1

Вывод:

Hi
0 голосов
/ 20 апреля 2019

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

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

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

0 голосов
/ 20 апреля 2019

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

$ 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++)) { ;}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...