Secara acak menggambar sejumlah garis dari file data

13

Saya punya daftar data, seperti

12345
23456
67891
-20000
200
600
20
...

Asumsikan ukuran kumpulan data ini (yaitu baris file) adalah N. Saya ingin menggambar mgaris secara acak dari file data ini. Oleh karena itu, output harus berupa dua file, satu file termasuk mbaris data ini, dan yang lainnya mencakup N-mbaris data.

Apakah ada cara untuk melakukan itu menggunakan perintah Linux?

pengguna288609
sumber
1
Apakah Anda khawatir tentang urutan garis? misalnya. Apakah Anda ingin mempertahankan urutan sumber, atau Anda ingin urutan itu menjadi acak sekaligus pilihan garis menjadi acak?
Peter.O

Jawaban:

18

Ini mungkin bukan cara yang paling efisien tetapi berhasil:

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

Dengan $mmengandung jumlah garis.

Rob Wouters
sumber
@ penggunaunknown, urus sort -Rkeacakan. Tidak yakin jika Anda menurunkan jawaban untuk itu, tetapi cari dulu di halaman manual.
Rob Wouters
2
Catatan yang sort -Rtidak persis mengurutkan inputnya secara acak: itu mengelompokkan garis yang identik. Jadi jika input misalnya foo, foo, bar, bardan m = 2, maka satu file akan mengandung foodan yang lainnya akan mengandung kedua bars. GNU coreutils juga memiliki shuf, yang mengacak jalur input. Selain itu, Anda tidak memerlukan file sementara .
Gilles 'SO- stop being evil'
mengapa tidak shuf <file> |head -n $m?
emanuele
@emanuele: Karena kita membutuhkan kepala dan ekor dalam dua file terpisah.
Rob Wouters
5

Skrip bash / awk ini memilih baris secara acak, dan mempertahankan urutan asli di kedua file output.

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

Keluaran, berdasarkan pada data dalam pertanyaan.

12345
23456
200
600
========
67891
-20000
20
Peter.O
sumber
4

Seperti semua hal Unix, Ada Utilitas untuk TM Itu .

Program hari ini: split
splitakan membagi file dengan berbagai cara, -bbyte, -lbaris, -njumlah file output. Kami akan menggunakan -lopsi. Karena Anda ingin memilih garis acak dan bukan hanya yang pertama m, kami akan sortfile secara acak terlebih dahulu. Jika Anda ingin membaca sort, lihat jawaban saya di sini .

Sekarang, kode yang sebenarnya. Ini cukup sederhana, sungguh:

sort -R input_file | split -l $m output_prefix

Ini akan membuat dua file, satu dengan mgaris dan satu dengan N-mgaris, bernama output_prefixaadan output_prefixab. Pastikan madalah file yang lebih besar yang Anda inginkan atau Anda akan mendapatkan beberapa file dengan panjang m(dan satu dengan N % m).

Jika Anda ingin memastikan bahwa Anda menggunakan ukuran yang benar, berikut ini sedikit kode untuk melakukannya:

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

Sunting: Telah menjadi perhatian saya bahwa beberapa sortimplementasi tidak memiliki -Rbendera. Jika sudah perl, Anda bisa menggantinya perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.

Kevin
sumber
1
Sayangnya, sort -Rtampaknya hanya ada di beberapa versi semacam (mungkin versi gnu). Untuk platform lain saya menulis alat yang disebut 'randline' yang tidak melakukan apa-apa selain mengacak stdin. Ini di beesbuzz.biz/code untuk siapa saja yang membutuhkannya. (Saya cenderung mengacak konten file cukup banyak.)
lembut
1
Catatan yang sort -Rtidak persis mengurutkan inputnya secara acak: itu mengelompokkan garis yang identik. Jadi jika input misalnya foo, foo, bar, bardan m = 2, maka satu file akan mengandung foodan yang lainnya akan mengandung kedua bars. GNU coreutils juga memiliki shuf, yang mengacak jalur input. Juga, Anda dapat memilih nama file output dengan menggunakan headdan tailbukansplit .
Gilles 'SO- stop being evil'
4

Jika Anda tidak keberatan menata ulang baris dan Anda memiliki GNU coreutils (yaitu pada Linux yang tidak tertanam atau Cygwin, tidak terlalu kuno sejak shufmuncul di versi 6.0), shuf("acak") mengatur ulang baris file secara acak. Jadi Anda dapat mengacak file dan mengirim baris m pertama ke dalam satu file dan sisanya ke yang lain.

Tidak ada cara ideal untuk melakukan pengiriman itu. Anda tidak bisa hanya rantai headdan tailkarena headakan buffer di depan. Anda dapat menggunakan split, tetapi Anda tidak mendapatkan fleksibilitas sehubungan dengan nama file output. Anda bisa menggunakan awk, tentu saja:

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

Anda dapat menggunakan sed, yang tidak jelas tetapi mungkin lebih cepat untuk file besar.

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

Atau Anda dapat menggunakan teeuntuk menggandakan data, jika platform Anda memiliki /dev/fd; tidak apa-apa jika m kecil:

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

Dengan mudah, Anda dapat menggunakan awk untuk mengirimkan setiap baris secara bergantian. Perhatikan bahwa awk tidak pandai menginisialisasi generator angka acaknya; keacakan tidak hanya pasti tidak cocok untuk kriptografi, tetapi bahkan tidak terlalu baik untuk simulasi numerik. Benih akan sama untuk semua doa awk pada sistem apa pun dengan periode satu detik.

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

Jika Anda membutuhkan keacakan yang lebih baik, Anda dapat melakukan hal yang sama di Perl, yang menanamkan RNG dengan baik.

<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 'SANGAT berhenti menjadi jahat'
sumber
@Gilles: Sebagai awkcontoh: -v N=$(wc -l <file) -v m=4... dan itu hanya mencetak garis "acak" ketika nilai acak kurang dari $m, daripada mencetak $mgaris acak ... Tampaknya perlmungkin melakukan hal yang sama dengan rand , tapi saya tidak tahu perlcukup baik untuk melewati kesalahan kompilasi: kesalahan sintaks pada -e baris 7, dekat ") print"
Peter.O
@ Peter.O Terima kasih, itulah yang muncul dari mengetik di browser dan mengedit dengan sembarangan. Saya telah memperbaiki kode awk dan perl.
Gilles 'SO- stop being evil'
Semua 3 metode bekerja dengan baik dan cepat .. terima kasih (+1) ... Saya perlahan mendapatkan kepalaku sekitar perl ... dan itu adalah split file yang sangat menarik dan berguna dalam shufcontoh.
Peter.O
Masalah buffereing? . Apakah saya melewatkan sesuatu? The head catcombo menyebabkan hilangnya data sebagai berikut tes kedua 3-4 .... UJI 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } .. UJI 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... wc -lhasil untuk output dari UJI 1-2 adalah 5000 5000 (baik), tapi untuk UJI 3-4 adalah 5000 4539 (tidak baik) .. Perbedaannya tergantung pada ukuran file yang terlibat ... Berikut ini tautan ke kode pengujian
Peter.O
@ Peter.O Benar lagi, terima kasih. Memang, headbaca di depan; apa yang dibaca di depan dan tidak dicetak dibuang. Saya telah memperbarui jawaban saya dengan kurang elegan tetapi (saya cukup yakin) solusi yang benar.
Gilles 'SANGAT berhenti menjadi jahat'
2

Dengan asumsi m = 7dan 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

Catatan: Jika Anda mengganti 7dengan variabel seperti $1atau $m, Anda harus menggunakan seq, bukan {from..to}-notasi, yang tidak melakukan ekspansi variabel.

Ia bekerja dengan menghapus baris demi baris dari file, yang semakin pendek dan lebih pendek, sehingga nomor baris, yang dapat dihapus, harus semakin kecil.

Ini tidak boleh digunakan untuk file yang lebih panjang, dan banyak baris, karena untuk setiap angka, rata-rata, setengah file harus dibaca untuk tanggal 1, dan seluruh file untuk kode sed kedua .

Pengguna tidak diketahui
sumber
Dia membutuhkan file dengan garis yang dihapus juga.
Rob Wouters
Saya pikir "termasuk baris data m ini" seharusnya berarti including themtetapi garis asli juga - karena itu including, tidak consisting of, dan tidak menggunakan only, tapi saya kira interpretasi Anda adalah, apa yang dimaksud user288609. Saya akan menyesuaikan skrip saya sesuai.
pengguna tidak diketahui
Kelihatan bagus. `` ``
Rob Wouters
@ pengguna tidak dikenal: Anda +1salah meletakkannya di tempat. Itu harus di rnd=$((RANDOM%(N-i)+1))mana N = 21 dalam contoh Anda .. Saat ini menyebabkan sedcrash ketika rnddievaluasi 0. .. Juga, itu tidak berskala sangat baik dengan semua file yang ditulis ulang. mis. 123 detik untuk mengekstrak 5.000 baris acak dari 10.000 baris file vs. 0,03 detik untuk metode yang lebih langsung ...
Peter.O
@ Peter.O: Anda benar (terkoreksi) dan Anda benar.
pengguna tidak dikenal