Perl-код, который составляет список всех слов, следующих за данной строкой в ​​текстовом файле - PullRequest
0 голосов
/ 24 августа 2010

Это сложно описать, но полезно при извлечении данных из вывода, с которым я имею дело (я надеюсь использовать этот код для большого количества целей)

Вот пример: Скажем, у меня есть текстовый файл со словами и некоторыми специальными символами ($, #,! И т. Д.), Который гласит:


бла-бла
бла добавить это слово в список: 1234.56 бла бла
бла-бла
бла, не забудьте добавить это слово в список: PINAPPLE бла-бла
А для бонусных баллов
было бы неплохо знать, что скрипт
мог бы добавить это слово в список: 1! @ # $% ^ & * () [] {} ;: '", <.> /? asdf blah blah
бла-бла


Как видно из примера, я хотел бы добавить любое «слово» (определяемое как любая строка, не содержащая пробелов в этом контексте) к некоторой форме списка, так что я могу извлечь элементы списка в виде списка [2] list [3] или list (4) list (5), или что-то в этом роде.

Это было бы очень универсально, и после некоторого опроса в другой теме и на другом форуме я надеюсь, что наличие его в perl сделает его относительно быстрым в выполнении - поэтому он будет работать хорошо даже для больших текстовых файлов. Я намереваюсь использовать это для чтения данных из выходных файлов, созданных из разных программ, независимо от структуры выходного файла, то есть, если я знаю строку для поиска, я могу получить данные.

Ответы [ 3 ]

2 голосов
/ 24 августа 2010

Если строка для поиска одинакова, пусть Perl выполнит обработку , используя поисковую фразу в качестве разделителя входных записей :

open my $fh, '<', 'test.dat' or die "can't open $!"; # usual way of opening a file

my @list;                                            # declare empty array 'list' (results)
$/= 'add this word to the list:';                    # define custom input  record seperator

while( <$fh> ) {                                     # read records one by one
   push @list, $1 if /(\S\S*)/
}
close $fh;                                           # thats it, close file!

print join "\n", @list;                              # this will list the results

выше "почти нормально", оно сохранит первое слово файла в $ list [0] из-за способа обработки.Но этот способ позволяет очень легко понять (imho)

blah                 <== first word of the file
1234.56
PINAPPLE
1!@#$%^&*()[]{};:'",<.>/?asdf

Q : почему бы просто не просмотреть строки с одним регулярным выражением по всем данным (как уже было предложено здесь).Потому что, по моему опыту, обработка записей с регулярным выражением для каждой записи (вероятно, очень сложное регулярное выражение в реальном случае) будет быстрее - особенно для очень больших файлов.Вот причина.

Тест в реальном мире

Чтобы подтвердить это утверждение, я провел несколько тестов с файлом данных объемом 200 МБ, содержащим 10000 ваших маркеров.Источник теста следующий:

use strict;
use warnings;
use Benchmark qw(timethese cmpthese);
use FILE::Slurp;
# 'data.dat', a 200MB data file, containing 10_000
# markers: 'add this word to the list:' and a
# one of different data items after each.

my $t = timethese(10,
 {
  'readline+regex' => sub { # trivial reading line-by-line
                     open my $fh, '<', 'data.dat' or die "can't open $!"; 
                     my @list;                                            
                     while(<$fh>) { 
                        push @list,$1 if /add this word to the list:\s*(\S+)/
                     }
                     close $fh;                                           
                     return scalar @list;   
                  },
  'readIRS+regex' => sub { # treat each 'marker' as start of an input record
                     open my $fh, '<', 'data.dat' or die "can't open $!"; 
                     $/= 'add this word to the list:';    # new IRS                
                     my @list;                                            
                     while(<$fh>) { push @list, $1 if /(\S+)/ }       
                     close $fh;                                           
                     return scalar @list;   
                  },
  'slurp+regex' => sub { # read the whole file and apply regular expression
                     my $filecontents = File::Slurp::read_file('data.dat');
                     my @list = $filecontents =~ /add this word to the list:\s*(\S+)/g;
                     return scalar @list;
                  },
 }
);
cmpthese( $t ) ;

, который выводит следующие результаты синхронизации:

Benchmark: timing 10 iterations of readIRS+regex, readline+regex, slurp+regex...
readIRS+regex: 43 wallclock secs (37.11 usr +  5.48 sys = 42.59 CPU) @  0.23/s (n=10)
readline+regex: 42 wallclock secs (36.47 usr +  5.49 sys = 41.96 CPU) @  0.24/s (n=10)
slurp+regex: 142 wallclock secs (135.85 usr +  4.98 sys = 140.82 CPU) @  0.07/s (n=10)
               s/iter    slurp+regex  readIRS+regex readline+regex
slurp+regex      14.1             --           -70%           -70%
readIRS+regex    4.26           231%             --            -1%
readline+regex   4.20           236%             1%             --

, что в основном означает, что простое считывание строк и блочное считывание с помощью пользовательских IRSпримерно в 2,3 раза быстрее (один проход за ~ 4 с), чем выкачивать файл и сканировать с помощью регулярного выражения.

В основном это говорит о том, что если вы обрабатываете файлы такого размера в такой системе, как моя ;-)Вы должны читать построчно , если ваша проблема поиска находится в одной строке , и читать с помощью пользовательского разделителя входных записей , если ваша проблема поиска включает более одной строки (мои 0,02 доллара США).

Хотите тоже сделать тест?Вот этот:

use strict;
use warnings;

sub getsomerandomtext {
    my ($s, $n) = ('', (shift));
    while($n --> 0) {
        $s .= chr( rand(80) + 30 );
        $s .= "\n" if rand($n) < $n/10
    }
    $s x 10
}

my @stuff = (
 q{1234.56}, q{PINEAPPLE}, q{1!@#$%^&*()[]{};:'",<.>/?asdf}
);

my $fn = 'data.dat';
open my $fh, '>', $fn or die $!;

my $phrase='add this word to the list:';
my $x = 10000;

while($x --> 0) {
   print $fh
      getsomerandomtext(1000),  ' ',
      $phrase, ' ', $stuff[int(rand(@stuff))],  ' ',
      getsomerandomtext(1000), "\n",
}

close $fh;
print "done.\n";

создает 200 МБ входного файла 'data.dat'.

С уважением

rbo

2 голосов
/ 24 августа 2010

Я думаю, что в вашем вопросе есть пропущенные слова :) Но это звучит как то, что вы хотите (при условии, что даже «большие текстовые файлы» помещаются в памяти - если нет, то вы бы перебирали строку за строкой в ​​$ listвместо).

my $filecontents = File::Slurp::read_file("filename");
@list = $filecontents =~ /add this word to the list: (\S+)/g;
0 голосов
/ 24 августа 2010

Как насчет:

my(@list);
my $rx = qr/.*add this word to the list: +(\S+)/;
while (<>)
{
     while (m/$rx/)
     {
          push @list, $1;
          s/$rx//;
     }
}

Это позволяет использовать длинные строки, содержащие более одного маркера добавления. Если определенно может быть только один, замените внутренний while на if. (За исключением, конечно, того, что я использовал жадный '.*', который сводит на нет все до последнего совпадения матча ...)

my(@list);
my $rx = qr/(?:.*?)add this word to the list: +(\S+)/;
while (<>)
{
     while (m/$rx/)
     {
          push @list, $1;
          s/$rx//;
     }
}

с выбираемым маркером:

my $marker = "add this word to the list:";
my(@list);
my $rx = qr/(?:.*?)$marker\s+(\S+)/;
while (<>)
{
     while (m/$rx/)
     {
          push @list, $1;
          s/$rx//;
     }
}

Без повторов:

my $marker = "add this word to the list:";
my(%hash);
my(@list);
my $rx = qr/(?:.*?)$marker\s+(\S+)/;
while (<>)
{
     while (m/$rx/)
     {
          push @list, $1 unless defined $hash{$1};
          $hash{$1} = 1;
          s/$rx//;
     }
}

Etc.


И, как указывает @ysth, вам (I) замена не нужна - Perl DWIM правильно соответствует g-квалифицированному соответствию во внутреннем цикле:

#!/bin/perl -w
use strict;
my(@list);
my(%hash);
my($marker) = "add this word to the list:";
my $rx = qr/(?:.*?)$marker\s+(\S+)/;
while (<>)
{
    while (m/$rx/g)
    {
        push @list, $1 unless defined $hash{$1};
        $hash{$1} = 1;
    }
}

foreach my $i (@list)
{
    print "$i\n";
}
...