2010-04-29 22 views
6

¿Hay alguna manera de hacer que una subrutina envíe datos de vuelta mientras se está procesando? Por ejemplo (este ejemplo se usa simplemente para ilustrar): una subrutina lee un archivo. Mientras lee el archivo, si se cumple alguna condición, entonces "devuelva" esa línea y siga procesando. Sé que hay quienes responderán, ¿por qué querrías hacer eso? y ¿por qué no solo ...?, pero realmente me gustaría saber si esto es posible.¿Puede una subrutina Perl devolver datos pero seguir procesando?

+1

¿Puede explicar lo que quiere decir con "devolver una línea y seguir procesando"? ¿Quiere decir al mismo tiempo que se procesa la línea devuelta? ¿Quiere decir que la rutina comienza donde quedó? – frankc

+3

Esto no está muy claro. Normalmente, hay un hilo de ejecución, y en este caso está en la persona que llama o en quien llama, por lo que es imposible regresar y continuar el proceso. ¿Estás hablando de usar hilos? –

+1

Creo que está describiendo corutinas. Algunos lenguajes implementan esto con una declaración llamada "rendimiento" o "retorno de rendimiento" que devuelve un valor a la persona que llama, y ​​también hace que la siguiente llamada a la función * reanude * ejecutando desde el punto de rendimiento, con todas las variables locales intactas . –

Respuesta

7

Una forma común de implementar este tipo de funcionalidad es una función de devolución de llamada:

{ 
    open my $log, '>', 'logfile' or die $!; 
    sub log_line {print $log @_} 
} 

sub process_file { 
    my ($filename, $callback) = @_; 
    open my $file, '<', $filename or die $!; 
    local $_; 
    while (<$file>) { 
     if (/some condition/) { 
      $callback->($_) 
     } 
     # whatever other processing you need .... 
    } 
} 

process_file 'myfile.txt', \&log_line; 

o sin siquiera nombrar a la devolución de llamada:

process_file 'myfile.txt', sub {print STDERR @_}; 
+1

@Eric: ¿Qué pasa con el cierre al principio? – Zaid

+0

Esto no devuelve datos al nivel superior. Es solo una subrutina llamando a otra subrutina. –

+0

solo hay una buena medida, ya que '$ log' solo lo usa' log_line', no hay motivo para que tenga el alcance –

0

Si realmente quieres hacer esto, puedes usar el enhebrado. Una opción sería bifurcar un hilo separado que lea el archivo y cuando encuentre una cierta línea, colóquelo en una matriz que se comparte entre hilos. Luego, el otro hilo podría tomar las líneas, tal como se encuentran, y procesarlas. Aquí hay un ejemplo que lee un archivo, busca una 'X' en la línea de un archivo y realiza una acción cuando se encuentra.

use strict; 
use threads; 
use threads::shared; 

my @ary : shared; 

my $thr = threads->create('file_reader'); 

while(1){ 
    my ($value); 
    { 
     lock(@ary); 
     if ($#ary > -1){ 
      $value = shift(@ary); 
      print "Found a line to process: $value\n"; 
     } 
     else{ 
      print "no more lines to process...\n"; 
     }    
    } 

    sleep(1); 
    #process $value 
} 


sub file_reader{ 

      #File input 
    open(INPUT, "<test.txt"); 
    while(<INPUT>){ 
     my($line) = $_; 
     chomp($line); 

     print "reading $line\n"; 

     if ($line =~ /X/){ 
      print "pushing $line\n"; 
      lock(@ary); 
      push @ary, $line; 
     } 
     sleep(4) 
    } 
    close(INPUT); 
} 

Prueba este código como el archivo test.txt:

line 1 
line 2X 
line 3 
line 4X 
line 5 
line 6 
line 7X 
line 8 
line 9 
line 10 
line 11 
line 12X 
4

Algunos lenguajes ofrecen este tipo de función por medio de "generators" o "coroutines", pero Perl no. La página del generador vinculada anteriormente tiene ejemplos en Python, C# y Ruby (entre otros).

+2

tipo de. Los generadores detienen el procesamiento cuando devuelven un resultado y comienzan de nuevo cuando se solicita el siguiente resultado. No hay nada sobre ellos que creará un segundo hilo de ejecución. – mob

+2

@mobrule: La pregunta no mencionaba los hilos, sino simplemente "seguir procesando". Los generadores permiten que el contexto local del generador persista entre llamadas, lo que puede considerarse lo mismo (a nivel conceptual). –

+0

Cierto. No está claro lo que significa el OP por "mantener el procesamiento". – mob

2

La manera más fácil de hacer esto en Perl es probablemente con una solución de tipo iterador. Por ejemplo, aquí tenemos una subrutina que forma una closure durante un gestor de archivo:

open my $fh, '<', 'some_file.txt' or die $!; 
my $iter = sub { 
    while(my $line = <$fh>) { 
     return $line if $line =~ /foo/; 
    } 

    return; 
} 

Los sub itera a través de las líneas hasta que encuentra uno que coincida con el patrón /foo/ y luego lo devuelve, o si no devuelve nada. (undef en contexto escalar.) Debido a que el identificador de archivo $fh se define fuera del alcance del sub, permanece residente en la memoria entre las llamadas. Lo que es más importante, se conserva su estado, incluida la posición de búsqueda actual en el archivo. De modo que cada llamada a la subrutina se reanuda leyendo el archivo donde se dejó por última vez.

Para utilizar el iterador:

while(defined(my $next_line = $iter->())) { 
    # do something with each line here 
} 
+2

Parece una versión complicada de 'while (<$fh>) {next unless/foo /; ...} '. Observe que la subrutina de su iterador terminó de procesarse y se devolvió. No siguió procesando. –

+0

@brian: En este sencillo ejemplo sí, su código es más corto y más simple, pero los cierres ofrecen mucha más flexibilidad. No es demasiado difícil refactorizar lo anterior para que las funciones de transformación de secuencia (por ejemplo, filtros como el anterior, pero también mutadores y generadores) puedan encadenarse entre sí o unirse a la LINQ. El efecto es similar al mapa, etc. pero con la diferencia de que la evaluación se puede "pausar" en cualquier momento. –

+0

No estoy discutiendo contra los cierres, eso es todo. Encontrará que muchos de mis módulos de CPAN hacen exactamente lo que usted dice, y hablo de las cosas que menciona en varios capítulos de Mastering Perl. Sin embargo, siguen siendo solo subrutinas a las que llamas y cuyo valor de retorno utilizas. No hay magia especial más allá de eso. –

-1

Si su idioma admite cierres, puede hacer algo como esto:

Por cierto, la función no continuaría procesando el archivo, se ejecutaría justo cuando lo llames, por lo que puede que no sea lo que necesitas.

(Este es un javascript como pseudo-código)

function fileReader (filename) { 
    var file = open(filename); 

    return function() { 
     while (s = file.read()) { 
      if (condition) { 
       return line; 
      } 
     } 
     return null; 
    }  
} 

a = fileReader("myfile"); 
line1 = a(); 
line2 = a(); 
line3 = a(); 
+0

¿Desea comentar, downvoter? –

+2

Quizás es porque la pregunta está etiquetada como 'perl' y tu respuesta proporciona una implementación de javascript. – dreamlax

+0

Dice pseudo código, no sé la sintaxis en perl para hacer eso. (Havent use perl en tantos años). –

-1

Qué pasa con un sub recursiva? Re-open Los manejadores de archivos existentes no restablecen el número de línea de entrada, por lo que continúa desde donde se dejó.

Aquí hay un ejemplo donde la subrutina process_file imprime párrafos "\n\n" separados por línea en blanco que contienen foo.

sub process_file { 

    my ($fileHandle) = @_; 
    my $paragraph; 

    while (defined(my $line = <$fileHandle>) and not eof(<$fileHandle>)) { 

     $paragraph .= $line; 
     last unless length($line); 
    } 

    print $paragraph if $paragraph =~ /foo/; 
    goto &process_file unless eof($fileHandle); 
     # goto optimizes the tail recursion and prevents a stack overflow 
     # redo unless eof($fileHandle); would also work 
} 

open my $fileHandle, '<', 'file.txt'; 
process_file($fileHandle); 
+0

si el archivo es grande, esto va a devorar muy rápidamente su pila de llamadas. corrija la última línea de 'process_file' con:' goto & process_file a menos que eof $ fileHandle; 'para eliminar el crecimiento de la pila durante la recursión final –

+0

@Eric: Implementado – Zaid

+0

=> necesita eliminar la lista de argumentos como se muestra en mi primer comentario. la sintaxis 'goto ⊂' llamará a '& sub' con el valor actual de' @ _'. como está actualmente, llamará a process_file, y luego cuando regrese, perl intentará ingresar el valor de retorno como una etiqueta. –

3

El módulo Coro parece que sería útil para este problema, aunque no tengo ni idea de cómo funciona y ni idea de si lo hace lo que anuncia.

Cuestiones relacionadas