Каков наиболее эффективный способ анализа текстового файла с помощью Perl? - PullRequest
6 голосов
/ 02 марта 2011

Хотя это довольно простой вопрос, я не могу найти похожий вопрос, поэтому, пожалуйста, связывайтесь с ним, если вам известен существующий вопрос / решение по SO.


У меня есть .txt файл размером около 2 МБ и длиной около 16 000 строк. Длина каждой записи составляет 160 символов с коэффициентом блокировки 10. Это более старый тип структуры данных, который почти выглядит как файл с разделителями табуляции, но разделение по одиночным символам / пробелам.

Во-первых, я glob каталог для .txt файлов - в каталоге не может быть более одного файла за раз, поэтому эта попытка может быть неэффективной сама по себе.

my $txt_file = glob "/some/cheese/dir/*.txt";

Затем я открываю файл с этой строкой:

open (F, $txt_file) || die ("Could not open $txt_file");

Согласно словарю данных для этого файла, я анализирую каждое «поле» из каждой строки, используя функцию Perl substr() в цикле while.

while ($line = <F>)
{
$nom_stat   = substr($line,0,1);
$lname      = substr($line,1,15);
$fname      = substr($line,16,15);
$mname      = substr($line,31,1);
$address    = substr($line,32,30);
$city       = substr($line,62,20);
$st         = substr($line,82,2);
$zip        = substr($line,84,5);
$lnum       = substr($line,93,9);
$cl_rank    = substr($line,108,4);
$ceeb       = substr($line,112,6);
$county     = substr($line,118,2);
$sex        = substr($line,120,1);
$grant_type = substr($line,121,1);
$int_major  = substr($line,122,3);
$acad_idx   = substr($line,125,3);
$gpa        = substr($line,128,5);
$hs_cl_size = substr($line,135,4);
}


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

Кто-нибудь может предложить более эффективный / предпочтительный метод?

Ответы [ 4 ]

8 голосов
/ 03 марта 2011

Мне кажется, что вы здесь работаете с полями фиксированной ширины. Это правда? Если это так, вам нужна функция unpack. Вы предоставляете шаблон для полей, и он будет извлекать информацию из этих полей. Доступно учебное пособие , а информация о шаблоне находится в документации для pack, что является логическим обратным unpack. В качестве базового примера просто:

my @values = unpack("A1 A15 A15 ...", $line);

где «A» означает любой текстовый символ (насколько я понимаю), а число - это сколько. unpack - это искусство, которое используют некоторые люди, но я считаю, что этого будет достаточно для базового использования.

4 голосов
/ 03 марта 2011

Единственное регулярное выражение, скомпилированное и кэшированное с использованием опции /o, является самым быстрым подходом. Я запустил ваш код тремя способами, используя модуль Benchmark, и получил:

         Rate unpack substr regexp
 unpack 2.59/s     --   -59%   -67%
 substr 6.23/s   141%     --   -21%
 regexp 7.90/s   206%    27%     --

Вводом был файл с 20k строк, каждая строка имела одинаковые 160 символов (16 повторений символов 0123456789). Так что это тот же размер ввода, что и данные, с которыми вы работаете.

Метод Benchmark::cmpthese() выводит вызовы подпрограммы от медленнее до быстрее . Первый столбец сообщает нам, сколько раз в секунду может выполняться подпрограмма. Подход с использованием регулярных выражений самый быстрый. Не распаковывать, как я заявлял ранее. Извините за это.

Код теста ниже. Печатные заявления там как проверки здравомыслия. Это было с Perl 5.10.0, созданным для darwin-thread-multi-2level.

#!/usr/bin/env perl
use Benchmark qw(:all);
use strict;

sub use_substr() {
    print "use_substr(): New itteration\n";
    open(F, "<data.txt") or die $!;
    while (my $line = <F>) {
        my($nom_stat, 
           $lname,   
           $fname,      
           $mname,    
           $address,     
           $city,    
           $st,       
           $zip,         
           $lnum,        
           $cl_rank,
           $ceeb,    
           $county,
           $sex,     
           $grant_type,
           $int_major, 
           $acad_idx,  
           $gpa,   
           $hs_cl_size) = (substr($line,0,1),
                           substr($line,1,15),
                           substr($line,16,15),
                           substr($line,31,1),
                           substr($line,32,30),
                           substr($line,62,20),
                           substr($line,82,2),
                           substr($line,84,5),
                           substr($line,93,9),
                           substr($line,108,4),
                           substr($line,112,6),
                           substr($line,118,2),
                           substr($line,120,1),
                           substr($line,121,1),
                           substr($line,122,3),
                           substr($line,125,3),
                           substr($line,128,5),
                           substr($line,135,4));
       #print "use_substr(): \$lname = $lname\n";
       #print "use_substr(): \$gpa   = $gpa\n";
    }    
    close(F);
    return 1;
}

sub use_regexp() {
    print "use_regexp(): New itteration\n";
    my $pattern = '^(.{1})(.{15})(.{15})(.{1})(.{30})(.{20})(.{2})(.{5})(.{9})(.{4})(.{6})(.{2})(.{1})(.{1})(.{3})(.{3})(.{5})(.{4})';
    open(F, "<data.txt") or die $!;
    while (my $line = <F>) {
        if ( $line =~ m/$pattern/o ) {
            my($nom_stat, 
               $lname,   
               $fname,      
               $mname,    
               $address,     
               $city,    
               $st,       
               $zip,         
               $lnum,        
               $cl_rank,
               $ceeb,    
               $county,
               $sex,     
               $grant_type,
               $int_major, 
               $acad_idx,  
               $gpa,   
               $hs_cl_size) = ( $1,
                                $2,
                                $3,
                                $4,
                                $5,
                                $6,
                                $7,
                                $8,
                                $9,
                                $10,
                                $11,
                                $12,
                                $13,
                                $14,
                                $15,
                                $16,
                                $17,
                                $18);
            #print "use_regexp(): \$lname = $lname\n";
            #print "use_regexp(): \$gpa   = $gpa\n";
        }
    }    
    close(F);
    return 1;
}

sub use_unpack() {
    print "use_unpack(): New itteration\n";
    open(F, "<data.txt") or die $!;
    while (my $line = <F>) {
        my($nom_stat, 
           $lname,   
           $fname,      
           $mname,    
           $address,     
           $city,    
           $st,       
           $zip,         
           $lnum,        
           $cl_rank,
           $ceeb,    
           $county,
           $sex,     
           $grant_type,
           $int_major, 
           $acad_idx,  
           $gpa,   
           $hs_cl_size) = unpack(
               "(A1)(A15)(A15)(A1)(A30)(A20)(A2)(A5)(A9)(A4)(A6)(A2)(A1)(A1)(A3)(A3)(A5)(A4)(A*)", $line
               );
        #print "use_unpack(): \$lname = $lname\n";
        #print "use_unpack(): \$gpa   = $gpa\n";
    }
    close(F);
    return 1;
}

# Benchmark it
my $itt = 50;
cmpthese($itt, {
        'substr' => sub { use_substr(); },
        'regexp' => sub { use_regexp(); },
        'unpack' => sub { use_unpack(); },
    }
);
exit(0)
0 голосов
/ 03 марта 2011

Вы можете сделать что-то вроде:

while ($line = <F>){
   if ($line =~ /(.{1}) (.{15}) ........ /){
     $nom_stat = $1;
     $lname = $2;
     ...
   }
}

Я думаю, что это быстрее, чем ваше предложение, но я не уверен, что это самое быстрое решение, но я думаю, что оно вполне может быть.

0 голосов
/ 03 марта 2011

Сделайте разделение на каждой строке, например:

my @values = split(/\s/,$line);

, а затем работайте со своими значениями.

...