Dibuja aleatoriamente un cierto número de líneas de un archivo de datos

13

Tengo una lista de datos, como

12345
23456
67891
-20000
200
600
20
...

Suponga que el tamaño de este conjunto de datos (es decir, líneas de archivo) es N. Quiero dibujar mlíneas al azar de este archivo de datos. Por lo tanto, el resultado debe ser dos archivos, uno es el archivo que incluye estas mlíneas de datos y el otro incluye N-mlíneas de datos.

¿Hay alguna manera de hacerlo usando un comando de Linux?

usuario288609
fuente
1
¿Te preocupa la secuencia de líneas? p.ej. ¿Desea mantener el orden de origen o desea que esa secuencia sea aleatoria y que la elección de las líneas sea aleatoria?
Peter.O

Respuestas:

18

Puede que esta no sea la forma más eficiente, pero funciona:

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

Con que $mcontiene el número de líneas.

Rob Wouters
fuente
@userunknown, sort -Rse encarga de la aleatoriedad. No estoy seguro si rechazó la respuesta para eso, pero primero búsquela en la página de manual.
Rob Wouters
2
Tenga en cuenta que sort -Rno ordena exactamente su entrada al azar: agrupa líneas idénticas. Así que si la entrada es, por ejemplo foo, foo, bar, bary m = 2, entonces un archivo contendrá ambos foos y el otro contendrá ambos bars. GNU coreutils también tiene shuf, que aleatoriza las líneas de entrada. Además, no necesita un archivo temporal .
Gilles 'SO- deja de ser malvado'
¿por qué no shuf <file> |head -n $m?
emanuele
@emanuele: Porque necesitamos tanto la cabeza como la cola en dos archivos separados.
Rob Wouters
5

Este script bash / awk elige líneas al azar y mantiene la secuencia original en ambos archivos de salida.

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

Salida, basada en los datos de la pregunta.

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

Como con todas las cosas de Unix, hay una utilidad para esa TM .

Programa del día: split
splitdividirá un archivo de muchas maneras diferentes, -bbytes, -llíneas,-n número de archivos de salida. Usaremos la -lopción. Como desea elegir líneas aleatorias y no solo la primera m, primero sortarchivaremos al azar. Si desea leer sobre sort, consulte mi respuesta aquí .

Ahora, el código real. Es bastante simple, realmente:

sort -R input_file | split -l $m output_prefix

Esto creará dos archivos, uno con mlíneas y otro con N-mlíneas, llamadosoutput_prefixaa y output_prefixab. Asegúrese de que msea ​​el archivo más grande que desea o obtendrá varios archivos de longitud m(y uno con N % m).

Si desea asegurarse de usar el tamaño correcto, aquí hay un pequeño código para hacerlo:

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

Editar: Me ha llamado la atención que algunas sortimplementaciones no tienen una -Rbandera. Si es así perl, puede sustituirlo perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.

Kevin
fuente
1
Desafortunadamente, sort -Rparece estar solo en algunas versiones de tipo (probablemente la versión gnu). Para otras plataformas escribí una herramienta llamada 'randline' que no hace más que aleatorizar stdin. Está en beesbuzz.biz/code para cualquiera que lo necesite. (Tiendo a barajar bastante el contenido de los archivos)
Fluffy
1
Tenga en cuenta que sort -Rno ordena exactamente su entrada al azar: agrupa líneas idénticas. Así que si la entrada es, por ejemplo foo, foo, bar, bary m = 2, entonces un archivo contendrá ambos foos y el otro contendrá ambos bars. GNU coreutils también tiene shuf, que aleatoriza las líneas de entrada. Además, puede elegir los nombres de los archivos de salida utilizando heady en taillugar desplit .
Gilles 'SO- deja de ser malvado'
4

Si no le importa reordenar las líneas y tiene coreutils de GNU (es decir, en Linux o Cygwin no integrados, no demasiado antiguos desde que shufapareció en la versión 6.0),shuf ("barajar") reordena las líneas de un archivo al azar. Por lo tanto, puede mezclar el archivo y enviar las primeras m líneas a un archivo y el resto a otro.

No hay una forma ideal de hacer ese despacho. No puedes simplemente encadenar heady tailporque headse amortiguaría. Puede usar split, pero no obtiene ninguna flexibilidad con respecto a los nombres de los archivos de salida. Puedes usar awk, por supuesto:

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

Puede usarlo sed, que es oscuro pero posiblemente más rápido para archivos grandes.

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

O puede usar teepara duplicar los datos, si su plataforma lo ha hecho /dev/fd; está bien si m es pequeño:

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

Portablemente, puede usar awk para despachar cada línea por turno. Tenga en cuenta que awk no es muy bueno para inicializar su generador de números aleatorios; la aleatoriedad no solo definitivamente no es adecuada para la criptografía, sino que ni siquiera es muy buena para las simulaciones numéricas. La semilla será la misma para todas las invocaciones awk en cualquier sistema dentro de un período de un segundo.

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

Si necesita una mejor aleatoriedad, puede hacer lo mismo en Perl, que siembra decentemente su 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 'SO- deja de ser malvado'
fuente
@Gilles: Por awkejemplo: -v N=$(wc -l <file) -v m=4... y solo imprime una línea "aleatoria" cuando el valor aleatorio es menor que $m, en lugar de imprimir $mlíneas aleatorias ... Parece que perlpuede estar haciendo lo mismo con rand , pero no no sé lo perlsuficiente como para superar un error de compilación: error de sintaxis en la línea 7, cerca de ") print"
Peter.O
@ Peter.O Gracias, eso es lo que viene de escribir en un navegador y editar sin cuidado. He arreglado el código awk y perl.
Gilles 'SO- deja de ser malvado'
Los 3 métodos funcionan bien y rápido ... gracias (+1) ... lentamente estoy entendiendo perl ... y ese es un archivo particularmente interesante y útil dividido en el shufejemplo.
Peter.O
¿Un problema de amortiguación? . ¿Me estoy perdiendo de algo? El head catcombo causa la pérdida de datos en la segunda prueba siguiente 3-4 ... PRUEBA 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } ... PRUEBA 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... los wc -lresultados para las salidas de PRUEBA 1-2 son 5000 5000 (bueno), pero para PRUEBA 3-4 son 5000 4539 (no es bueno). La diferencia varía según los tamaños de archivo involucrados ... Aquí hay un enlace a mi código de prueba
Peter.O
@ Peter.O De nuevo, gracias. De hecho, headlee por delante; lo que lee más adelante y no se imprime se descarta. He actualizado mi respuesta con soluciones menos elegantes pero correctas (estoy razonablemente seguro).
Gilles 'SO- deja de ser malvado'
2

Asumiendo m = 7y 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: Si reemplaza 7con una variable como $1o $m, debe usar seq, no la {from..to}notación, que no hace expansión de variable.

Funciona eliminando línea por línea del archivo, que se acorta cada vez más, por lo que el número de línea, que se puede eliminar, debe hacerse cada vez más pequeño.

Esto no debe usarse para archivos más largos y para muchas líneas, ya que para cada número, en promedio, el medio archivo debe leerse para el primero y el archivo completo para el segundo código sed .

usuario desconocido
fuente
Necesita un archivo con las líneas que también se eliminan.
Rob Wouters
Pensé que "incluir estas m líneas de datos" debería significar including thempero también las líneas originales, por lo tanto including, no consisting of, y no usar only, pero supongo que su interpretación es lo que el usuario 288609 quería decir. Ajustaré mi script en consecuencia.
Usuario desconocido
Se ve bien. `` ``
Rob Wouters
@user unknown: tienes el +1en el lugar equivocado. Debería ser rnd=$((RANDOM%(N-i)+1))donde N = 21 en su ejemplo. Actualmente hace sedque se bloquee cuando rndse evalúa 0. .. Además, no se escala muy bien con toda esa reescritura de archivos. por ejemplo, 123 segundos para extraer 5.000 líneas al azar a partir de un archivo de 10.000 línea frente a 0,03 segundos de un método más directo ...
Peter.O
@ Peter.O: Tienes razón (corregida) y tienes razón.
usuario desconocido el