如何使用Perl从文本文件中检索第N行?

时间:2010-09-10 22:34:05

标签: perl

如何在长文本列表中打印第1行,第10行,第20行...(不是数组索引)编号。 当然,以下内容不起作用:

for(my $i=0; $i<=$arr_size; $i+=10){
    print $arr[$i],"\n";
}

非常感谢提前。

5 个答案:

答案 0 :(得分:10)

如果您正在阅读文件句柄:

while (my $line = <$fh>) {
    if ($. == 1 or not $. % 10) {
        print $line;
    }
}

如果你的标量包含许多行,如:

my $s = join "", map { "$_\n" } "a" .. "z";

然后,您可以通过在open期间传递对它的引用来将标量视为文件:

open my $fh, "<", \$s
    or die "could not open in-memory file: $!";

然后使用上面的解决方案。

总而言之,你得到了

#!/usr/bin/perl

use strict;
use warnings;

my $s = join "", map { "$_\n" } "a" .. "z";

open my $fh, "<", \$s
    or die "could not open in-memory file: $!";

while (my $line = <$fh>) {
    if ($. == 1 or not $. % 10) {
        print "$. $line";
    }
}

请注意,此技巧仅在您使用PerlIO构建perl时才有效,但这是自Perl 5.8以来的默认设置。如果您的perl版本未使用Perl IO编译,则需要从CPAN中获取IO::Scalar

对于真正疯狂的怪异程度,你可以在内存文件中使用Tie::File

#!/usr/bin/perl

use strict;
use warnings;

use Tie::File;

my $s = join "", map { "$_\n" } "a" .. "z";

open my $fh, "<", \$s
    or die "could not open in-memory file: $!";

tie my @lines, "Tie::File", $fh
    or die "could not tie in-memory file: $!";

my $i = 0;
while (defined $lines[$i]) {
    print "$lines[$i]\n";
} continue {
    $i += 10;
}

答案 1 :(得分:5)

以下是使用/g修饰符的正则表达式的方法。

my $count = 0;
my @found;
while($text =~ /\G(.*)\n/g) {
    next if $count++ % 10 != 0;

    push @found, $1;
}

对于小于100行的小字符串,我比Chas的标量ref文件句柄解决方案快约50%,但在1000行以上,它的速度提高了约20%。

Chas的文件句柄解决方案更安全(如果你写错误的正则表达式,你可以让自己有一个无限循环),更简单,并且不会明显更慢,也不会使用更多的内存。使用它。

答案 2 :(得分:3)

这是一个基准测试,使用我的简单文件句读取与Schwern的正则表达式和Chas。的绑定。

这是在我的Mac Pro上运行的Perl 5.12.2:

                 Rate Chas. Chas. modified drewk Schwern Chas. sane drewk2 brian
Chas.          70.0/s    --           -33%  -94%    -94%       -95%   -95%  -96%
Chas. modified  104/s   48%             --  -91%    -91%       -92%   -93%  -94%
drewk          1163/s 1560%          1019%    --     -5%       -15%   -23%  -35%
Schwern        1220/s 1641%          1073%    5%      --       -11%   -20%  -32%
Chas. sane     1370/s 1856%          1218%   18%     12%         --   -10%  -23%
drewk2         1515/s 2064%          1358%   30%     24%        11%     --  -15%
brian          1786/s 2450%          1618%   54%     46%        30%    18%    --

这是同一台机器上的Perl 5.10.1:

                 Rate Chas. Chas. modified drewk Schwern Chas. sane drewk2 brian
Chas.          66.9/s    --           -35%  -94%    -95%       -95%   -96%  -96%
Chas. modified  103/s   54%             --  -91%    -92%       -93%   -93%  -94%
drewk          1111/s 1560%           981%    --    -17%       -22%   -27%  -40%
Schwern        1333/s 1892%          1197%   20%      --        -7%   -12%  -28%
Chas. sane     1429/s 2034%          1290%   29%      7%         --    -6%  -23%
drewk2         1515/s 2164%          1374%   36%     14%         6%     --  -18%
brian          1852/s 2667%          1702%   67%     39%        30%    22%    --

这些结果并没有让我感到惊讶。 Tie :: File似乎比它应该慢,但我预计它会很慢。它很漂亮,但是我发现Tie :: File在性能方面通常很差,因为它不是一个很难开始的界面。如果您需要随机和重复访问,这很好,但对于单次传递顺序访问,它是错误的工具。查斯。比我认为他在这个例子中真正需要的工作做得多一些。我们知道我们想要的行的索引,所以我们可以只取一个绑定数组。切片比查看每一行的while循环快约150%。

为了看到极端的结果,我将这些行重复了1000次(因此,文件中大约有1,300,000行):

 $scalar = slurp( $file ) x 1000;

这些是Perl 5.12.2上的大文件的结果:

                  Rate Chas. Chas. modified drewk drewk2 Schwern Chas. sane brian
Chas.          0.695/s    --           -32%  -91%   -94%    -94%       -95%  -96%
Chas. modified  1.02/s   46%             --  -86%   -91%    -92%       -93%  -94%
drewk           7.38/s  962%           626%    --   -34%    -39%       -47%  -59%
drewk2          11.2/s 1512%          1002%   52%     --     -7%       -19%  -38%
Schwern         12.1/s 1635%          1086%   63%     8%      --       -13%  -33%
Chas. sane      13.9/s 1896%          1264%   88%    24%     15%         --  -23%
brian           18.0/s 2495%          1674%  144%    61%     50%        30%    --

drewk创建新阵列的解决方案现在显示了他们的扩展问题。因为它们并不比其他解决方案简单,并且它们有这么大的缺点,所以没有理由这样做。

这是我的基准程序。程序中有一点点差异。我的解决方案(和Chas。的第一个解决方案)得到问题文本中提到的第1行,第10行,第20行等等。如破损代码中所述,其他解决方案获得第1行,第11行,第21行等等。但这对于基准测试并不重要。

#!perl
use strict;
use warnings;

use File::Slurp qw(slurp);
use Tie::File;
use Benchmark qw(cmpthese);
use vars qw($scalar);

chomp( my $file = `perldoc -l perlfaq5` );
#$file = '/Users/brian/Desktop/lines';
print "file is $file\n";
$scalar = slurp( $file );

cmpthese( 1000, {
    'Chas.'          => \&chas,
    'Schwern'        => \&schwern,
    'brian'          => \&brian,
    'Chas. modified' => \&chas_modified,
    'Chas. sane'     => \&chas_sane,
    'drewk'          => \&drewk,
    'drewk2'         => \&drewk2,
    });

sub drewk {
   my @arr = split(/\n/, $scalar);
   my @found;
   for(my $i=0; $i<=$#arr; $i+=10){
    #  print "drewk[$i] $arr[$i]\n";
      push @found, $arr[$i];
    }
}
sub drewk2 {
   my $i=0;
   my @found;
   foreach(split(/\n/, $scalar)) {
      next if $i++ % 10;
#      print "drewk2[$i] $_\n";
      push @found, $_;
   }
}
sub schwern {
    my $count = 0;
    my @found;
    while($scalar =~ /\G(.*)\n/g) {
        next if $count++ % 10 != 0;
#       print "schwern[$count] $1\n";
        push @found, $1;
        }
    }

sub chas {
    open my $fh, "<", \$scalar;

    tie my @lines, "Tie::File", $fh
        or die "could not tie in-memory file: $!";

    my $i = 0;
    my @found = ();
    while (defined $lines[$i]) {
        # print "chas[$i]: $lines[$i]\n";
        push @found, $lines[$i];
        } continue {
            $i += 10;
        }   
    }

sub chas_modified {
    open my $fh, "<", \$scalar;

    tie my @lines, "Tie::File", $fh
        or die "could not tie in-memory file: $!";

    my $highest_multiple = int( $#lines / 10 ) ;
    my @found = @lines[ map { $_ * 10  - ($_?1:0) } 0 .. $highest_multiple ]; 
    #print join "\n", @found;
    }

sub chas_sane {
    open my $fh, "<", \$scalar;

    my @found;
    while (my $line = <$fh>) {
        if ($. == 1 or not $. % 10) {
            #print "chas_sane[$.] $line";
            push @found, $_;
            }
        }
    }

sub brian {
    open my $fh, '<', \$scalar;
    my @found = scalar <$fh>;
    while( <$fh> ) {
        next if $. % 10;
        #print "brian[$.] $_";
        push @found, $_;
        }
    }

答案 3 :(得分:-1)

如果Schern的评论是正确的,你的"list of text" means its in a $scalar解决方法是使用Perl split,那么你可以使用你编写的代码:

sub drewk {
   my @arr = split(/\n/, $scalar);
   for(my $i=0; $i<=$#arr; $i+=10){
       #print $arr[$i],"\n";
    }
}

不是使用C风格循环,而是可以编写非常易读的Perl习语来做同样快速的事情:

sub drewk2 {
   my $i=0;
   my @found;
   foreach(split(/\n/, $scalar)) {
      next if $i++ % 10;
      #print "$_\n";
      push @found, $_;
   }
}

将这些插入brian的基准,你会得到非常有竞争力的结果:

                 Rate    Chas. Chas. modified Schwern    drewk    brian   drewk2
Chas.          86.1/s       --           -37%    -95%     -95%     -96%     -96%
Chas. modified  136/s      59%             --    -92%     -92%     -93%     -94%
Schwern        1695/s    1869%          1142%      --      -3%     -14%     -22%
drewk          1754/s    1939%          1186%      4%       --     -11%     -19%
brian          1961/s    2178%          1337%     16%      12%       --     -10%
drewk2         2174/s    2426%          1493%     28%      24%      11%       --

(this on a iMac 2.93 GHz Intel COre i7 with Perl 5.10)

您没有发布导致发布循环的代码上下文。也许你做过这样的事情:

   $scalar="line 1\nline 2\n ... line n";

   push @arr, $scalar;
   #or
   $arr[0]=$scalar;

认为\n会导致行以不同的数组元素结束?下次发布上下文......

----编辑:

原始帖子指出How can I print 1st, 10th, 20th... lines (not array index) number in a long list of text.如果通过“长文本列表”表示兆字节和千兆字节,请使用Brian或Chas的文件句柄方法。它光滑,快速,数据不会在内存中重复。如果“长文本列表”是RAM很多的大小,你可以使用split,/ \ n / g等等或者对你和数据有意义的任何东西。

答案 4 :(得分:-1)

my $lineno = 10;
open FILE, "filename.txt";
my @arr = <FILE>;
print $arr[$lineno];