Disegna casualmente un certo numero di linee da un file di dati


13

Ho un elenco di dati, come

12345
23456
67891
-20000
200
600
20
...

Supponiamo che la dimensione di questo set di dati (ovvero le righe del file) sia N. Voglio disegnare casualmente mlinee da questo file di dati. Pertanto, l'output dovrebbe essere di due file, uno è il file che include queste mrighe di dati e l'altro include N-mrighe di dati.

C'è un modo per farlo usando un comando Linux?


1
Sei preoccupato per la sequenza di linee? per esempio. Vuoi mantenere l'ordine dei sorgenti o vuoi che quella sequenza sia casuale e che la scelta delle linee sia casuale?
Peter

Risposte:


18

Questo potrebbe non essere il modo più efficiente ma funziona:

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

Con $mcontenente il numero di righe.


@userunknown, sort -Rsi occupa della casualità. Non sono sicuro se hai annullato la votazione della risposta, ma prima cerca nella manpage.
Rob Wouters,

2
Nota che sort -Rnon ordina esattamente i suoi input in modo casuale: raggruppa linee identiche. Quindi, se l'ingresso è ad esempio foo, foo, bar, bare m = 2, poi un file conterrà entrambe le foos e l'altro conterrà entrambe le bars. Ha anche coreutils GNU shuf, che randomizza le linee di input. Inoltre, non è necessario un file temporaneo .
Gilles 'SO- smetti di essere malvagio' il

perchè no shuf <file> |head -n $m?
emanuele,

@emanuele: Perché abbiamo bisogno sia della testa che della coda in due file separati.
Rob Wouters,

5

Questo script bash / awk sceglie le linee in modo casuale e mantiene la sequenza originale in entrambi i file di 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

Output, basato sui dati nella domanda.

12345
23456
200
600
========
67891
-20000
20

4

Come per tutte le cose Unix, c'è un'utilità per quella TM .

Programma del giorno: split
splitdividerà un file in molti modi diversi, -bbyte, -llinee, -nnumero di file di output. Useremo l' -lopzione. Dal momento che vuoi scegliere linee casuali e non solo la prima m, sortil file verrà prima casualmente. Se vuoi leggere sort, fai riferimento alla mia risposta qui .

Ora, il codice attuale. È abbastanza semplice, davvero:

sort -R input_file | split -l $m output_prefix

Questo renderà due file, uno con mlinee e uno con N-mlinee, chiamati output_prefixaae output_prefixab. Assicurati che msia il file più grande che desideri o otterrai diversi file di lunghezza m(e uno con N % m).

Se vuoi assicurarti di utilizzare la dimensione corretta, ecco un piccolo codice per farlo:

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

Modifica: è venuto alla mia attenzione che alcune sortimplementazioni non hanno una -Rbandiera. Se hai perl, puoi sostituire perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.


1
Sfortunatamente, sort -Rsembra essere solo in alcune versioni dell'ordinamento (probabilmente la versione gnu). Per altre piattaforme ho scritto uno strumento chiamato "randline" che non fa altro che randomizzare lo stdin. È su beesbuzz.biz/code per chiunque ne abbia bisogno. (Tendo a mescolare parecchio il contenuto del file.)
soffice

1
Nota che sort -Rnon ordina esattamente i suoi input in modo casuale: raggruppa linee identiche. Quindi, se l'ingresso è ad esempio foo, foo, bar, bare m = 2, poi un file conterrà entrambe le foos e l'altro conterrà entrambe le bars. Ha anche coreutils GNU shuf, che randomizza le linee di input. Inoltre, puoi scegliere i nomi dei file di output usando heade tailinvece displit .
Gilles 'SO- smetti di essere malvagio' il

4

Se non ti dispiace riordinare le linee e hai coreutils GNU (cioè su Linux non incorporato o Cygwin, non troppo antico da quando è shufapparso nella versione 6.0), shuf("shuffle") riordina le linee di un file in modo casuale. Quindi puoi mescolare il file e inviare le prime m righe in un file e il resto in un altro.

Non esiste un modo ideale per fare quella spedizione. Non puoi semplicemente concatenare heade tailperché headsarebbe tampone in anticipo. È possibile utilizzare split, ma non si ottiene alcuna flessibilità rispetto ai nomi dei file di output. Puoi awkovviamente usare :

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

È possibile utilizzare sed, che è oscuro ma forse più veloce per file di grandi dimensioni.

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

Oppure puoi usare teeper duplicare i dati, se la tua piattaforma ha /dev/fd; va bene se m è piccolo:

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

Portabilmente, puoi usare awk per spedire ogni riga a turno. Nota che awk non è molto bravo a inizializzare il suo generatore di numeri casuali; la casualità non è sicuramente non solo adatta per la crittografia, ma nemmeno molto buona per le simulazioni numeriche. Il seme sarà lo stesso per tutte le invocazioni awk su qualsiasi sistema entro un periodo di un secondo.

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

Se hai bisogno di una migliore casualità, puoi fare la stessa cosa in Perl, che semina decentemente il suo 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: Per l' awkesempio: -v N=$(wc -l <file) -v m=4... e stampa una linea "casuale" solo quando il valore casuale è inferiore $m, anziché stampare $mlinee casuali ... Sembra che perlpotrebbe fare la stessa cosa con rand , ma io conosci perlabbastanza bene per superare un errore di compilazione: errore di sintassi alla riga 7, vicino a ") print"
Peter.O

@ Peter.O Grazie, questo è ciò che deriva dalla digitazione in un browser e dalla modifica incauta. Ho corretto il codice awk e perl.
Gilles 'SO- smetti di essere malvagio' il

Tutti e 3 i metodi funzionano bene e velocemente .. grazie (+1) ... Mi sto lentamente spostando la testa perl ... e questo è un file particolarmente interessante e utile diviso shufnell'esempio.
Peter

Un problema di buffering? . Mi sto perdendo qualcosa? La head catcombinazione provoca la perdita di dati nel seguente secondo test 3-4 .... TEST 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } .. TEST 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... i wc -lrisultati per gli output di TEST 1-2 sono 5000 5000 (buono), ma per TEST 3-4 sono 5000 4539 (non buono) .. La differenza varia in base alle dimensioni del file coinvolto ... Ecco un link al mio codice di prova
Peter.O

@ Peter.O Proprio di nuovo, grazie. Anzi, headlegge avanti; ciò che legge in anticipo e che non viene stampato viene scartato. Ho aggiornato la mia risposta con soluzioni meno eleganti ma (ne sono ragionevolmente sicuro).
Gilles 'SO- smetti di essere malvagio' il

2

Supponendo m = 7e 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

Nota: se si sostituisce 7con una variabile come $1o $m, è necessario utilizzare seq, non la {from..to}notazione, che non esegue l'espansione della variabile.

Funziona eliminando riga per riga dal file, che diventa sempre più breve, quindi il numero di riga, che può essere rimosso, deve diventare sempre più piccolo.

Questo non dovrebbe essere usato per file più lunghi e per molte righe, poiché per ogni numero, in media, il mezzo file deve essere letto per il 1o e l'intero file per il 2o codice sed .


Ha bisogno di un file con le righe che vengono rimosse.
Rob Wouters,

Pensavo che "includere queste m righe di dati" dovesse significare anche including themle righe originali - quindi including, non consisting ofe non usare only, ma immagino che la tua interpretazione sia ciò che intendeva user288609. Adatterò la mia sceneggiatura di conseguenza.
Utente sconosciuto il

Sembra buono. `` ``
Rob Wouters

@utente sconosciuto: hai il +1posto sbagliato. Dovrebbe essere rnd=$((RANDOM%(N-i)+1))dove N = 21 nel tuo esempio. Attualmente causa un sedarresto anomalo quando rndviene valutato 0. .. Inoltre, non si adatta molto bene con tutta quella riscrittura dei file. es. 123 secondi per estrarre 5.000 righe casuali da un file di 10.000 righe rispetto a 0,03 secondi per un metodo più diretto ...
Peter.O

@ Peter.O: hai ragione (corretto) e hai ragione.
utente sconosciuto dal
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.