【问题标题】:Best way to skip a header when reading in from a text file in Perl?从 Perl 中的文本文件读取时跳过标题的最佳方法是什么?
【发布时间】:2013-01-01 19:56:52
【问题描述】:

我从 Perl 中的制表符描述的文件中抓取了几列。文件的第一行与其他行完全不同,因此我想尽可能快速高效地跳过该行。

这是我目前所拥有的。

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";
    }
}

有没有更好的方法来做到这一点,也许没有 $firstLine?或者有没有办法直接从第 2 行开始读取 INFILE?

提前致谢!

【问题讨论】:

  • 附带说明,数组切片和连接将消除大量重复代码。 print OUTFILE "$columns[0]\t\t"; print OUTFILE join("\t", @columns[1,2,3,11,12,15,20,21]); print OUTFILE "\n";
  • 我将不得不考虑加入。我是 Perl 的新手。谢谢!
  • 再清理一下:print OUTFILE "$columns[0]\t\t" . join("\t", @columns[1,2,3,11,12,15,20,21]) . "\n";

标签: performance perl input io conditional


【解决方案1】:

让我们得到一些关于这方面的数据。我对每个人的技术进行了基准测试...

#!/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); },
};

由于这是 I/O,它可能会受到超出 Benchmark.pm 调整能力的力的影响,因此我运行了几次并检查我得到了相同的结果。

/usr/share/dict/words 是一个 2.4 兆的文件,大约有 240k 非常短的行。由于我们不处理线条,因此线条长度无关紧要。

我在每个例程中只做了少量的工作来强调技术之间的差异。我想做一些工作,以便通过更改读取文件的方式获得或损失多少性能产生一个现实的上限。

我在带有 SSD 的笔记本电脑上执行此操作,但它仍然是笔记本电脑。随着 I/O 速度的提高,CPU 时间变得更加重要。在具有快速 I/O 的机器上,技术更为重要。

这是每个例程每秒读取文件的次数。

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

我很震惊地发现 my @array = &lt;$fh&gt; 速度最慢,幅度很大。考虑到所有工作都在 perl 解释器中进行,我会认为这将是最快的。但是,它是唯一一个分配内存来保存所有行的方法,这可能是导致性能滞后的原因。

使用$. 是另一个惊喜。也许这就是访问魔法全局的成本,或者可能是进行数字比较。

而且,正如算法分析所预测的那样,将标头检查代码放在循环之外是最快的。但不是很多。如果您使用接下来的两个最快,可能还不足以担心。

【讨论】:

    【解决方案2】:

    您可以第一次为其分配一个虚拟变量:

    #!/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);
    

    【讨论】:

    • fh 不应该有 $,因为它是一个文件句柄。但这看起来是最有效的解决方案。谢谢!
    • 是词法文件句柄;这些天它实际上是首选。
    • @JimDavis 那些日子已经过去了。
    【解决方案3】:

    我总是使用$.(当前行号)来实现这一点:

    #!/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
    }
    

    【讨论】:

    • 作为一种通用技术,它的性能较低,因为您的循环现在必须在每次迭代时进行额外检查。它还会使循环变得混乱。
    • 性能损失是给定的,但由于它看起来更整洁,所以可以忽略不计,值得。如果您觉得它使循环变得混乱,那一定是品味问题。
    • “使循环混乱”意味着它增加了您必须了解的代码量才能知道循环内发生了什么,但它仅适用于第一次迭代。我将它与 Guru 的最佳案例进行比较,即将该代码放在循环之外,而不是 OP。
    【解决方案4】:

    您可以在文件句柄中读取文件,然后可以使用数组或 while 循环遍历行。对于 while 循环,@Guru 为您提供解决方案。对于数组,如下所示:

    #!/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);
    

    【讨论】:

    • 通过将整个文件存储在一个数组中,这可能会消耗大量内存。
    • 这比从磁盘顺序读取文件效率更高。而“大量的记忆”在 15 年前很重要。
    【解决方案5】:

    你的代码在这种形式下可能会更优雅:

    my $first;
    while (...) {
        $first++ or next; 
    
        # do whatever you want
    };
    

    但它仍然很好。 @Guru 的答案在 CPU 周期方面更好,但 i/o 通常比单个 if 消耗更多数量级。

    【讨论】:

      【解决方案6】:

      我有一个类似的问题/问题。我的解决方案如下 - 对于解压缩或 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";
      }
      

      我不知道基准测试,但它对我来说很好。

      最好的,

      砂光机

      【讨论】:

        【解决方案7】:

        对我来说,使用 splice 似乎是最简单、最干净的方法:

        open FILE, "<$ARGV[0]";
        my @file = <FILE>;
        splice(@file, 0, 1);
        

        完成。现在你的 @file 数组不再有第一行了。

        【讨论】:

          猜你喜欢
          • 2010-09-24
          • 2021-01-14
          • 2011-01-11
          • 1970-01-01
          • 2011-09-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多