Випадково намалюйте певну кількість рядків з файлу даних


13

У мене є список даних, як

12345
23456
67891
-20000
200
600
20
...

Припустимо, розмір цього набору даних (тобто рядків файлу) становить N. Я хочу довільно намалювати mрядки з цього файлу даних. Тому на виході повинні бути два файли, один - файл, що включає ці mрядки даних, а другий - N-mрядки даних.

Чи є спосіб це зробити за допомогою команди Linux?


1
Вас турбує послідовність рядків? напр. Ви хочете підтримувати вихідний порядок, чи ви хочете, щоб ця послідовність була сама по собі випадковою, а також вибір рядків був випадковим?
Пітер.О

Відповіді:


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

@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

4

Як і у всіх речах Unix, для цієї TM є утиліта .

Програма дня: 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 <>;'.


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; це нормально, якщо m невеликий:

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

Портативно, ви можете використовувати 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, який пристойно висіває свій RNG.

<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може робити те ж саме з рандів , але дон я не знаю perlдостатньо добре, щоб
уникнути

@ Peter.O Дякую, саме це відбувається від введення браузера та недбалого редагування. Я виправив код awk та perl.
Жиль "ТАК - перестань бути злим"

Усі 3 методи працюють добре і швидко .. дякую (+1) ... Я повільно обнімаю голову perl ... і це особливо цікавий і корисний розділений файл у shufприкладі.
Пітер.О

Проблема, що займається фальсифікацією? . Я щось пропускаю? 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}-нотацію, яка не робить розширення змінної.

Це працює, видаляючи з файла рядок за рядком, який стає все коротшим і коротшим, тому номер рядка, який можна видалити, повинен бути меншим і меншим.

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


Йому потрібен файл із вилученими рядками.
Роб Вутер

Я вважав, що "включення цих m рядків даних" має означати, including themале і оригінальні рядки - отже including, не consisting of, і не використовую only, але я думаю, що ваша інтерпретація полягає в тому, що означав user288609. Я відповідно відкоригую свій сценарій.
користувач невідомий

Виглядає чудово. `` ``
Роб Уотерс

@user невідомо: у вас +1неправильне місце. Він повинен бути rnd=$((RANDOM%(N-i)+1))там, де N = 21 у вашому прикладі. Наразі це призводить sedдо збоїв при rndоцінці 0. .. Крім того, він не дуже масштабується з усіма записами файлів. наприклад, 123 секунди для вилучення 5000 випадкових рядків з файлу рядків 10 000 проти 0,03 секунди для більш прямого методу ...
Peter.O

@ Peter.O: Ти маєш рацію (виправлено) і маєш рацію.
користувач невідомий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.