Как я могу извлечь N-ую строку из текстового файла, используя Perl? - PullRequest
2 голосов
/ 11 сентября 2010

Как мне напечатать 1-е, 10-е, 20-е ... строки (не индекс массива) в длинном списке текста.Конечно, следующее не работает:

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

Большое спасибо заранее.

Ответы [ 4 ]

10 голосов
/ 11 сентября 2010

Если вы читаете из файлового дескриптора:

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

Обратите внимание, что этот трюк работает, только если вы собрали perl с включенным PerlIO, но это было по умолчанию с Perl 5.8. Вам нужно будет извлечь IO::Scalar из CPAN, если ваша версия perl не была скомпилирована с Perl IO.

Для действительно безумных уровней странности вы можете использовать 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;
}
5 голосов
/ 11 сентября 2010

Вот как это можно сделать с помощью регулярного выражения, использующего модификатор /g.

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

    push @found, $1;
}

Я оцениваю его примерно на 50% быстрее, чем скалярное решение для файловых дескрипторов Chas для небольших строк длиной менее 100 строк, но на 1000 строк и выше оно выравнивается всего на 20% быстрее.

Решение для файлового дескриптора Chas безопаснее (если вы неправильно напишите регулярное выражение, вы можете получить бесконечный цикл), проще и не значительно медленнее и не использует больше памяти. Используйте это.

3 голосов
/ 11 сентября 2010

Вот эталонный тест, использующий моё решение простого чтения файловых файлов по сравнению с регулярным выражением Шверна и связью Часов.

Это Perl 5.12.2, работающий на моем Mac Pro:

                 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 часто плохо компенсирует производительность для хорошего интерфейса с чем-то, что было не так уж сложно начать. Хорошо, если вам нужен случайный и повторный доступ, но для последовательного доступа за один проход это неправильный инструмент. Чес. делает немного больше работы, чем я думаю, что он действительно нуждается в этом примере. Мы знаем индексы строк, которые нам нужны, поэтому мы можем просто взять кусок связанного массива. Срез примерно на 150% быстрее, чем цикл while, который просматривает каждую строку.

Чтобы увидеть экстремальный результат, я реплицировал строки в 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%    --

Решения Дрюка, создающие новые массивы, теперь показывают проблему масштабирования. Поскольку они не проще, чем другие решения, и у них есть этот большой недостаток, нет причин делать это таким образом.

Вот моя тестовая программа. Там очень небольшая разница в программах. Мое решение (и первое решение 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, $_;
        }
    }
0 голосов
/ 11 сентября 2010

Если комментарий Шерн верен, что ваш "list of text" means its in a $scalar один способ исправить это с помощью split в Perl, вы можете использовать код, который вы написали таким образом:

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, $_;
   }
}

Включив их в тест Брайана, вы получите очень конкурентоспособный результат:

                 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's или Chasподход дескриптор файла.Это гладко, быстро, и данные не будут дублироваться в памяти.Если «длинный список текста» имеет размер, в котором много оперативной памяти, вы можете использовать split, / \ n / g и т. Д. Или что-то еще, что может иметь смысл для вас и данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...