2013-01-18 11 views
8

Sto catturando alcune colonne da un file delimitato da tabulazioni in Perl. La prima riga del file è completamente diversa dalle altre righe, quindi vorrei saltare quella linea il più velocemente ed efficacemente possibile.Il modo migliore per saltare un'intestazione durante la lettura da un file di testo in Perl?

Questo è quello che ho finora.

my $firstLine = 1; 

while (<INFILE>){ 
    if($firstLine){ 
     $firstLine = 0; 
    } 
    else{ 
     my @columns = split (/\t+/); 
     print OUTFILE "$columns[0]\t\t$columns[1]\t$columns[2]\t$columns[3]\t$columns[11]\t$columns[12]\t$columns[15]\t$columns[20]\t$columns[21]\n"; 
    } 
} 

C'è un modo migliore per farlo, forse senza $ firstLine? O c'è un modo per iniziare a leggere INFILE dalla linea 2 direttamente?

Grazie in anticipo!

+5

Come nota a margine, le fette di array e unisciti eliminerebbe un sacco di quel codice ripetuto. 'print OUTFILE" $ columns [0] \ t \ t "; stampa join OUTFILE ("\ t", @columns [1,2,3,11,12,15,20,21]); print OUTFILE "\ n"; ' – Schwern

+0

Dovrò cercare di entrare. Sono nuovo di Perl. Grazie! – New2Perl

+0

Ripulito un po 'di più: 'print OUTFILE" $ columns [0] \ t \ t ". join ("\ t", @columns [1,2,3,11,12,15,20,21]). "\ n"; ' – New2Perl

risposta

26

Prendiamo alcuni dati su questo. I benchmark tecniche di tutti ...

#!/usr/bin/env perl 

sub flag_in_loop { 
    my $file = shift; 

    open my $fh, $file; 

    my $first = 1; 
    while(<$fh>) { 
     if($first) { 
      $first = 0; 
     } 
     else { 
      my $line = $_; 
     } 
    } 

    return; 
} 

sub strip_before_loop { 
    my $file = shift; 

    open my $fh, $file; 

    my $header = <$fh>; 
    while(<$fh>) { 
     my $line = $_; 
    } 

    return; 
} 

sub line_number_in_loop { 
    my $file = shift; 

    open my $fh, $file; 

    while(<$fh>) { 
     next if $. < 2; 

     my $line = $_; 
    } 

    return; 
} 

sub inc_in_loop { 
    my $file = shift; 

    open my $fh, $file; 

    my $first; 
    while(<$fh>) { 
     $first++ or next; 

     my $line = $_; 
    } 

    return; 
} 

sub slurp_to_array { 
    my $file = shift; 

    open my $fh, $file; 

    my @array = <$fh>; 
    shift @array; 

    return; 
} 


my $Test_File = "/usr/share/dict/words"; 
print `wc $Test_File`; 

use Benchmark; 

timethese shift || -10, { 
    flag_in_loop  => sub { flag_in_loop($Test_File); }, 
    strip_before_loop => sub { strip_before_loop($Test_File); }, 
    line_number_in_loop => sub { line_number_in_loop($Test_File); }, 
    inc_in_loop   => sub { inc_in_loop($Test_File); }, 
    slurp_to_array  => sub { slurp_to_array($Test_File); }, 
}; 

Poiché si tratta di I/O che può essere influenzato da forze al di là della capacità di Benchmark.pm per regolare, mi sono imbattuto più volte ed ho controllato ho ottenuto gli stessi risultati.

/usr/share/dict/words è un file da 2,4 megapixel con circa 240 k linee molto brevi. Poiché non stiamo elaborando le linee, la lunghezza della linea non dovrebbe avere importanza.

Ho fatto solo una piccola quantità di lavoro in ciascuna routine per enfatizzare la differenza tra le tecniche. Volevo fare un po 'di in modo da produrre un limite superiore realistico su quanto prestazioni andresti a guadagnare o perdere cambiando il modo in cui leggi i file.

L'ho fatto su un laptop con un SSD, ma è ancora un laptop. Con l'aumentare della velocità di I/O, il tempo della CPU diventa più significativo. La tecnica è ancora più importante su una macchina con I/O veloce.

Ecco quante volte ogni routine legge il file al secondo.

slurp_to_array:  4.5/s 
line_number_in_loop: 13.0/s 
inc_in_loop:   15.5/s 
flag_in_loop:  15.8/s 
strip_before_loop: 19.9/s 

Sono scioccato di scoprire che my @array = <$fh> è il più lento con un margine enorme. Avrei pensato che sarebbe stato il più veloce dato che tutto il lavoro sta accadendo all'interno dell'interprete perl. Tuttavia, è l'unico che alloca la memoria per contenere tutte le linee e che probabilmente rappresenta il ritardo delle prestazioni.

L'utilizzo di $. è un'altra sorpresa.Forse è il costo di accedere a una magia globale, o forse sta facendo un confronto numerico.

E, come previsto dall'analisi algoritmica, il codice di controllo dell'intestazione all'esterno del ciclo è il più veloce. Ma non di molto. Probabilmente non abbastanza da preoccuparti se stai usando i prossimi due tempi.

+0

Wow! Molto interessante! – New2Perl

+0

+1, molto bello in effetti .. – Guru

+0

+1. Confronto interessante davvero. – flesk

19

Si può solo assegnare una variabile dummy per la 1a volta:

#!/usr/bin/perl 
use strict; 
use warnings; 

open my $fh, '<','a.txt' or die $!; 

my $dummy=<$fh>; #First line is read here 
while(<$fh>){ 
     print ; 
} 
close($fh); 
+0

fh non dovrebbe avere $, in quanto è un handle di file. Ma questa sembra la soluzione più efficiente. Grazie! – New2Perl

+3

È un handle di file lessicale; in realtà è preferito in questi giorni. –

+0

TIL. Grazie per le informazioni! – New2Perl

2

Si può leggere un file in un handle di file e quindi può usare sia array o while per iterare su linee. per il ciclo while, @Guru ha la soluzione per te. per array, sarebbe come di seguito:

#!/usr/bin/perl 
use strict; 
use warnings; 

open (my $fh, '<','a.txt') or die "cant open the file: $! \n"; 
my @array = <$fh>; 

my $dummy = shift (@array); << this is where the headers are stored. 

foreach (@array) 
{ 
    print $_."\n"; 
} 
close ($fh); 
+0

Memorizzando l'intero file in un array, questo potenzialmente consuma molta memoria. – Schwern

+0

È molto più efficiente della lettura sequenziale del file dal disco. E "molta memoria" era importante 15 anni fa. –

7

Io uso sempre $. (numero di riga corrente) per raggiungere questo obiettivo:

#!/usr/bin/perl 
use strict; 
use warnings; 

open my $fh, '<', 'myfile.txt' or die "$!\n"; 

while (<$fh>) { 
    next if $. < 2; # Skip first line 

    # Do stuff with subsequent lines 
} 
+0

Come tecnica generale, questo è meno performante perché il tuo ciclo deve ora, a ogni iterazione, effettuare un controllo extra. Inoltre ingombra il ciclo. – Schwern

+0

La perdita di prestazioni è un dato, ma è abbastanza trascurabile per essere valsa la pena visto che sembra più ordinato. Se senti che riempie il circuito, deve essere una questione di gusti. – flesk

+0

"Cluster up the loop" significa che aggiunge alla quantità di codice che devi capire per sapere cosa sta succedendo all'interno del ciclo, ma si applica solo alla prima iterazione. Lo sto confrontando con il miglior caso di Guru che consiste nel mettere quel codice fuori dal ciclo, non nell'OP. – Schwern

0

Il tuo codice sarebbe probabilmente più elegante in questa forma:

my $first; 
while (...) { 
    $first++ or next; 

    # do whatever you want 
}; 

Ma va ancora bene. @ La risposta di Guru è migliore in termini di cicli della CPU, ma di solito l'I/O consuma ordini di grandezza più di un singolo se.

0

Ho avuto una domanda/problema simile. La mia soluzione è stata la seguente - sia per file decompressi o gzip:

print STDERR "\nReading input file...\n"; 
if ($file =~ /.gz$/) { 
    open(IN, "gunzip -c $file | grep -v '##' |") or die " *** ERROR ***  Cannot open pipe to [ $file ]!\n"; 
    } else { 
     open(IN, "cat $file | grep -v '##' |") or die " *** ERROR ***  Cannot open [ $file ]!\n"; 
} 

Non so su di benchmarking, ma funziona bene per me.

migliore,

Sander

Problemi correlati