Случайно нарисовать определенное количество линий из файла данных

13

У меня есть список данных, как

12345
23456
67891
-20000
200
600
20
...

Предположим, что размер этого набора данных (то есть строк файла) равен N. Я хочу случайным образом нарисовать mлинии из этого файла данных. Поэтому на выходе должны быть два файла, один из которых содержит эти mстроки данных, а другой - N-mстроки данных.

Есть ли способ сделать это с помощью команды Linux?

user288609
источник
1
Вас беспокоит последовательность строк? например. Хотите ли вы сохранить исходный порядок, или вы хотите, чтобы эта последовательность была сама по себе случайной, а также выбор случайных строк?
Peter.O

Ответы:

18

Это может быть не самый эффективный способ, но он работает:

shuf <file> > tmp
head -n $m tmp > out1
tail -n +$(( m + 1 )) tmp > out2

С $mуказанием количества строк.

Роб Ваутерс
источник
@userunknown, sort -Rзаботится о случайности. Не уверен, что вы отказались от ответа на этот вопрос, но сначала посмотрите его на странице руководства.
Роб Ваутерс
2
Обратите внимание, что sort -Rон не сортирует входные данные случайным образом: он группирует одинаковые строки. Таким образом , если вход , например foo, foo, bar, barи т = 2, то один файл будет содержать оба fooс , а другой будет содержать оба barс. GNU coreutils также имеет shuf, что рандомизирует входные строки. Кроме того, вам не нужен временный файл .
Жиль "ТАК ... перестать быть злым"
почему нет shuf <file> |head -n $m?
Эмануэле
@emanuele: потому что нам нужны голова и хвост в двух отдельных файлах.
Роб Ваутерс
5

Этот скрипт bash / awk выбирает строки случайным образом и сохраняет исходную последовательность в обоих выходных файлах.

awk -v m=4 -v N=$(wc -l <file) -v out1=/tmp/out1 -v out2=/tmp/out2 \
 'BEGIN{ srand()
         do{ lnb = 1 + int(rand()*N)
             if ( !(lnb in R) ) {
                 R[lnb] = 1
                 ct++ }
         } while (ct<m)
  } { if (R[NR]==1) print > out1 
      else          print > out2       
  }' file
cat /tmp/out1
echo ========
cat /tmp/out2

Вывод, основанный на данных в вопросе.

12345
23456
200
600
========
67891
-20000
20
Peter.O
источник
4

Как и во всем Unix, есть утилита для этого ТМ .

Программа дня: split
splitразделит файл по-разному: -bбайты, -lстроки, -nколичество выходных файлов. Мы будем использовать -lопцию. Так как вы хотите выбрать случайные строки, а не только первые m, мы сначала будем sortслучайным образом подавать файл. Если вы хотите прочитать о sort, обратитесь к моему ответу здесь .

Теперь актуальный код. Это довольно просто, на самом деле:

sort -R input_file | split -l $m output_prefix

Это создаст два файла, один со mстроками и один со N-mстроками, с именами output_prefixaaи output_prefixab. Убедитесь, что mфайл большего размера вы хотите, или вы получите несколько файлов длинойm (и один с N % m).

Если вы хотите убедиться, что вы используете правильный размер, вот небольшой код для этого:

m=10 # size you want one file to be
N=$(wc -l input_file)
m=$(( m > N/2 ? m : N - m ))
sort -R input_file | split -l $m output_prefix

Редактировать: до меня дошло, что в некоторых sortреализациях нет -Rфлага. Если у вас есть perl, вы можете заменить perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.

Kevin
источник
1
К сожалению, sort -Rпохоже, что только в некоторых версиях сортировки (вероятно, версия GNU). Для других платформ я написал инструмент под названием 'randline', который ничего не делает, кроме рандомизации stdin. Это на beesbuzz.biz/code для всех, кому это нужно. (Я часто перетасовываю содержимое файла.)
пушистый
1
Обратите внимание, что sort -Rон не сортирует входные данные случайным образом: он группирует одинаковые строки. Таким образом , если вход , например foo, foo, bar, barи т = 2, то один файл будет содержать оба fooс , а другой будет содержать оба barс. GNU coreutils также имеет shuf, что рандомизирует входные строки. Кроме того, вы можете выбрать имена выходных файлов, используя headи tailвместоsplit .
Жиль "ТАК ... перестать быть злым"
4

Если вы не возражаете переупорядочить строки и у вас есть GNU coreutils (то есть на не встроенных Linux или Cygwin, не слишком древних, так как shufпоявилось в версии 6.0), shuf(«shuffle») переупорядочивает строки файла случайным образом. Таким образом, вы можете перемешать файл и отправить первые m строк в один файл, а остальные - в другой.

Там нет идеального способа сделать эту отправку. Вы не можете просто зацепить headи tailпотому headчто буферизируете впереди. Вы можете использовать split, но вы не получаете никакой гибкости в отношении имен выходных файлов. Вы можете использовать awk, конечно:

<input shuf | awk -v m=$m '{ if (NR <= m) {print >"output1"} else {print} }'

Вы можете использовать sed, что неясно, но, возможно, быстрее для больших файлов.

<input shuf | sed -e "1,${m} w output1" -e "1,${m} d" >output2

Или вы можете использовать teeдля дублирования данных, если ваша платформа имеет /dev/fd; это нормально, если м мало:

<input shuf | { tee /dev/fd/3 | head -n $m >output1; } 3>&1 | tail -n +$(($m+1)) >output2

В частности, вы можете использовать awk для отправки каждой строки по очереди. Обратите внимание, что awk не очень хорош при инициализации генератора случайных чисел; случайность не только определенно не подходит для криптографии, но даже не очень хороша для численного моделирования. Начальное число будет одинаковым для всех вызовов awk в любой системе за период в одну секунду.

<input awk -v N=$(wc -l <input) -v m=3 '
    BEGIN {srand()}
    {
        if (rand() * N < m) {--m; print >"output1"} else {print >"output2"}
        --N;
    }'

Если вам нужна лучшая случайность, вы можете сделать то же самое в Perl, который прилично заполняет его ГСЧ.

<input perl -e '
    open OUT1, ">", "output1" or die $!;
    open OUT2, ">", "output2" or die $!;
    my $N = `wc -l <input`;
    my $m = $ARGV[0];
    while (<STDIN>) {
        if (rand($N) < $m) { --$m; print OUT1 $_; } else { print OUT2 $_; }
        --$N;
    }
    close OUT1 or die $!;
    close OUT2 or die $!;
' 42
Жиль "ТАК - прекрати быть злым"
источник
@Gilles: Для awkпримера: -v N=$(wc -l <file) -v m=4... и он печатает "случайную" строку только тогда, когда случайное значение меньше $m, чем печать $mслучайных строк ... Кажется, perlчто с rand можно делать то же самое , но я не perlнедостаточно хорошо знают , чтобы избежать ошибки компиляции: синтаксическая ошибка в -e строке 7, рядом с ") print"
Peter.O
@ Peter.O Спасибо, вот что получается, набрав в браузере и небрежно редактируя. Я исправил awk и perl код.
Жиль "ТАК - прекрати быть злым"
Все 3 метода работают хорошо и быстро .. спасибо (+1) ... я медленно осваиваю Perl ... и это особенно интересный и полезный файл, разбитый в shufпримере.
Peter.O
Проблема с буферизацией? , Я что-то пропустил? head catКомбо приводит к потере данных в следующем втором испытании 3-4 .... ТЕСТ 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } .. ТЕСТ 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... wc -lРезультаты для выходов TEST 1-2 являются 5000 5000 (хорошо), но TEST 3-4 это 5000 4539 (не хорошо) .. Различия варьируются в зависимости от размеров файла ... Вот ссылка на мой тестовый код
Peter.O
@ Peter.O Еще раз, спасибо. Действительно, headчитает впереди; то, что он читает и не распечатывает, отбрасывается. Я обновил свой ответ менее изящными, но (я вполне уверен) правильными решениями.
Жиль "ТАК - перестань быть злым"
2

Предполагая m = 7и N = 21:

cp ints ints.bak
for i in {1..7}
do
    rnd=$((RANDOM%(21-i)+1))
    # echo $rnd;  
    sed -n "${rnd}{p,q}" 10k.dat >> mlines 
    sed -i "${rnd}d" ints 
done

Примечание. Если вы замените 7переменную, например, $1или $m, вы должны будете использовать seq, а не {from..to}-notation, которая не выполняет расширение переменной.

Он работает путем удаления строки за строкой из файла, который становится все короче и короче, поэтому номер строки, которую можно удалить, должен становиться все меньше и меньше.

Это не должно использоваться для более длинных файлов и большого количества строк, поскольку для каждого числа в среднем необходимо прочитать половину файла для 1-го и весь файл для 2-го кода Sed .

Пользователь неизвестен
источник
Ему нужен файл со строками, которые тоже удалены.
Роб Ваутерс
Я подумал, что «включая эти m строк данных» должно означать, including themно также и исходные строки - следовательно including, нет consisting ofи не использую only, но, думаю, ваша интерпретация такова, что имел в виду пользователь 288609. Я буду корректировать свой сценарий соответственно.
пользователь неизвестен
Выглядит хорошо. `` ``
Роб Ваутерс
@ пользователь неизвестен: у вас не +1в том месте. Это должно быть rnd=$((RANDOM%(N-i)+1))где N = 21 в вашем примере. Это в настоящее время вызывает sedсбой, когда rndоценивается 0. Кроме того, он не очень хорошо масштабируется при переписывании файлов. например, 123 секунды для извлечения 5000 случайных строк из файла с 10000 строк против 0,03 секунды для более прямого метода ...
Peter.O
@ Peter.O: Вы правы (исправлено) и правы.
пользователь неизвестен