2009-05-13 14 views
6

Siguiendo con la pregunta this, necesito obtener exactamente n líneas al azar de un archivo (o stdin). Esto sería similar a head o tail, excepto que quiero algunos desde el medio.¿Cómo puedo obtener exactamente n líneas aleatorias de un archivo con Perl?

Ahora, además de recorrer el archivo con las soluciones a la pregunta vinculada, ¿cuál es la mejor manera de obtener exactamente las líneas n en una sola ejecución?

Como referencia, he intentado esto:

#!/usr/bin/perl -w 
use strict; 
my $ratio = shift; 
print $ratio, "\n"; 
while() { 
    print if ((int rand $ratio) == 1); 
} 

donde $ratio es el porcentaje aproximado de líneas quiero. Por ejemplo, si quiero 1 de cada 10 líneas:

random_select 10 a.list 

Sin embargo, esto no me da una cantidad exacta:

aaa> foreach i (0 1 2 3 4 5 6 7 8 9) 
foreach? random_select 10 a.list | wc -l 
foreach? end 
4739 
4865 
4739 
4889 
4934 
4809 
4712 
4842 
4814 
4817 

El otro pensamiento que tuve fue sorbiendo el archivo de entrada y luego elegir n al azar de la matriz, pero eso es un problema si tengo un archivo realmente grande.

¿Alguna idea?

Editar: Esto es un duplicado exacto de la pregunta this.

+1

No es éste un duplicado exacto de http://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix –

+0

sí, es. Lo siento. Voy a vincular los dos y votar para cerrarlo. –

+2

no, la otra pregunta permitía que la muestra estuviera desactivada, esta quiere un número exacto. – Alnitak

Respuesta

4

Aquí hay un buen algoritmo de un pase que acabo de presentar, que tiene complejidad de tiempo O (N) y complejidad de espacio O (M), para leer líneas M desde un archivo de N-line.

Supongamos M < = N.

  1. Let S ser el conjunto de líneas elegidas. Inicialice S en las primeras líneas M del archivo. Si el orden del resultado final es importante, baraje S ahora.
  2. Lea en la siguiente línea l. Hasta ahora, hemos leído n = M + 1 líneas totales. La probabilidad de que deseemos elegir l como una de nuestras líneas finales es por lo tanto M/n.
  3. Aceptar l con probabilidad M/n; use un RNG para decidir si acepta o rechaza l.
  4. Si se ha aceptado l, elija al azar una de las líneas en S y reemplácela por l.
  5. Repita los pasos 2 a 4 hasta que el archivo se haya agotado de líneas, incrementando n con cada nueva línea leída.
  6. Devuelve el conjunto S de las líneas elegidas.
+0

Bueno, pero creo que te refieres a M <= N – Alnitak

+0

El signo invertido es el eterno enemigo de los matemáticos. Reparado, con un suspiro. – kquinn

+0

también, ¿no hay un sesgo hacia las líneas M originales a menos que N >> M? – Alnitak

1

Posible solución:

  1. exploración una vez para contar el número de líneas
  2. decidir el número de línea para recoger al azar
  3. exploración otra vez, recoger la línea
+2

En stdin, el escaneo dos veces puede ser un problema. – Eyal

0

En pseudo- código:

use List::Util qw[shuffle]; 

# read and shuffle the whole file 
@list = shuffle(<>); 

# take the first 'n' from the list 
splice(@list, ...); 

Esta es la implementación más trivial, pero primero debe leer todo el archivo, lo que requerirá que tenga suficiente memoria disponible.

+1

esto no funcionará si el archivo es realmente enorme – kcwu

+0

Este es exactamente el problema que tuve. El archivo en el que estoy trabajando cuesta 63MB y demora una eternidad. –

+0

¿tamaño de archivo 63MB? ¿Cuántos MB RAM tienes? Creo que este tamaño no debería ser un problema. – kcwu

1
@result =(); 

$k = 0; 
while(<>) { 
    $k++; 
    if (scalar @result < $n) { 
     push @result, $_; 
    } else { 
     if (rand <= $n/$k) { 
      $result[int rand $n] = $_; 
     } 
    } 
} 

print for @result; 
+0

su prueba de Rand es incorrecta - debe ser $ n/$ k, no 1.0/$ k; – Alnitak

+0

gracias. corregido – kcwu

2

Esto toma un solo argumento de línea de comandos, que es el número de línea que desee, N. se llevan a cabo las primeras N líneas, ya que no podría ver nada más. A partir de entonces, usted al azar decide si tomar la siguiente línea. Y si lo hace, decide al azar qué línea en la lista actual de N para sobrescribir.

#!/usr/bin/perl 
my $bufsize = shift; 
my @list =(); 

srand(); 
while (<>) 
{ 
    push(@list, $_), next if (@list < $bufsize); 
    $list[ rand(@list) ] = $_ if (rand($./$bufsize) < 1); 
} 
print foreach @list; 
0

Aquí hay un código detallado de Perl que debería funcionar con archivos de gran tamaño.

El meollo de este código es que no almacena todo el archivo en la memoria, sino que solo almacena las compensaciones en el archivo.

Use tell para obtener las compensaciones. Luego seek a los lugares apropiados para recuperar las líneas.

Mejor especificación del archivo de destino y número de líneas para obtener se deja como un ejercicio para aquellos menos perezosos que yo. Esos problemas han sido bien resueltos.

#!/usr/bin/perl 

use strict; 
use warnings; 

use List::Util qw(shuffle); 

my $GET_LINES = 10; 

my @line_starts; 
open(my $fh, '<', 'big_text_file') 
    or die "Oh, fudge: $!\n"; 

do { 
    push @line_starts, tell $fh 
} while (<$fh>); 

my $count = @line_starts; 
print "Got $count lines\n"; 

my @shuffled_starts = (shuffle @line_starts)[0..$GET_LINES-1]; 

for my $start (@shuffled_starts) { 

    seek $fh, $start, 0 
     or die "Unable to seek to line - $!\n"; 

    print scalar <$fh>; 
} 
1

No es necesario conocer el número de línea real en el archivo. Simplemente busque en un lugar aleatorio y mantenga la siguiente línea . (La línea actual probablemente será una línea parcial.)

Este enfoque debe ser muy rápido para archivos grandes, pero no funcionará para STDIN. Diablos, nada de almacenar en caché todo el archivo en la memoria funcionará para STDIN. Entonces, si debe tener STDIN, no veo cómo puede ser rápido/barato para archivos grandes.

Puede detectar STDIN y cambiar a un enfoque en caché; de lo contrario, será rápido.

 
#!perl 
use strict; 

my $file='file.txt'; 
my $count=shift || 10; 
my $size=-s $file; 

open(FILE,$file) || die "Can't open $file\n"; 

while ($count--) { 
    seek(FILE,int(rand($size)),0); 
    $_=readline(FILE);       # ignore partial line 
    redo unless defined ($_ = readline(FILE)); # catch EOF 
    print $_; 
} 
+2

Tenga en cuenta que este enfoque * no * elegirá líneas uniformemente de un archivo. La probabilidad de que se elija una línea se ponderará por la longitud de la línea anterior; si todas las líneas tienen la misma longitud, esto no es problema. Pero si necesita una distribución estrictamente uniforme de líneas de un archivo con líneas de longitud variable, necesitará un enfoque diferente. – kquinn

+0

grrrr tienes razón ... bueno ... es * rápido * pero útil si la duración del registro es estática ... o bastante cercana. – rmeden

Cuestiones relacionadas