Как оптимально переместить строки с определенным шаблоном вверху в огромном файле с помощью Perl?

У меня есть огромный CSV-файл из почти 20 000 строк в следующем формате:

file,tools,edit,syntax,buffers
a,b,c,perl,d
a,w,c33,java,d
a,e,c,perl,d
a,s,c,python,d1
a,n,c,php,d3
d,r,hhh,cpp,d0
d,m,hhh,c#,d0
a,o,c,pdf,d3
a,f,c,python,dd
a,h,c,perl,dg
a,yb,c,c,ddf
a,b,c,perl,dt
wa,b,c33,java,d
d,buuu,hhh,cpp,d0
d44,b,hhh,nlp,d0
a,be,c,js,d4
wa,b,c33,java,d
wa,b,c33,python,d
wa,b,c33,python,d
wa,b,c33,c#,d
wa,b,c33,python,d
wa,b,c33,php,d
wa,b,c33,python,d
wa,b,c33,php,d
wa,b,c33,python,d
wa,b,c33,perl,d
wa,b,c33,php,d
wa,b,c33,java,d
wa,b,c33,python,d

Мне нужно поместить 2 строки шаблона с одинаковым синтаксисом (т.е. 4-й столбец) вверху. И тогда остальные строки будут как есть. Это означает, что первые две строки с синтаксисом «perl», затем «java», «python» и т. д. таким образом.

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

use strict;
use warnings;

open(FP, "+<mycsv.csv");

my %hash = ();
my $cur_pos;    


while(<FP>) {

    my $line = $_;
    chomp $line;
    #print "$line aaa\n";
    if($line =~ /^file\,tools,/) {next;}

    if($line =~ /^\w+\,\w+\,\w+,(\w+)\,.*$/) {
        my $type = $1;
        #print "type $type\n";

    if($hash{$type}->{count} < 2 ) {
        #print "--- here type = $type | lastpos = ", $hash{$type}->{lastpos} , "\n";
        $cur_pos = tell(FP);
        my $pos = tell(FP) - length($line); 
        if($hash{$type}->{lastpos} ) {

            my $lastpos = $hash{$type}->{lastpos};
            seek(FP, $lastpos, 1);
            print FP $line;
            seek(FP, $cur_pos, 1);
        } 

        $hash{$type}->{lastpos} = $pos;


    }
        if(exists $hash{$type} ) {
            $hash{$type}->{count} += 1;
        } else {
            $hash{$type}->{count} = 1;
        }


    }
}


close(FP);

Ожидаемый результат должен выглядеть следующим образом:

 file,tools,edit,syntax,buffers
    a,b,c,perl,d
    a,e,c,perl,d
    a,w,c33,java,d
    wa,b,c33,java,d
    a,s,c,python,d1
    a,f,c,python,dd
    a,n,c,php,d3
    wa,b,c33,php,d
    d,r,hhh,cpp,d0
    d,buuu,hhh,cpp,d0
    d,m,hhh,c#,d0
    wa,b,c33,c#,d
    a,o,c,pdf,d3 
    a,yb,c,c,ddf 
    d44,b,hhh,nlp,d0
    a,be,c,js,d4  
    a,h,c,perl,dg   
    a,b,c,perl,dt   
    wa,b,c33,java,d
    wa,b,c33,python,d
    wa,b,c33,python,d
    wa,b,c33,python,d
    wa,b,c33,python,d
    wa,b,c33,php,d
    wa,b,c33,python,d
    wa,b,c33,perl,d
    wa,b,c33,php,d
    wa,b,c33,java,d
    wa,b,c33,python,d

Любая помощь, чтобы заставить его работать, будет высоко оценена.

Спасибо.


person A.G.Progm.Enthusiast    schedule 16.04.2018    source источник
comment
Как должен выглядеть результат из введенных вами данных?   -  person i alarmed alien    schedule 16.04.2018
comment
@ialarmedalien Привет, обратите внимание, что я добавил вывод. Первые две строки каждого типа синтаксиса объединяются вместе и помещаются вверху. Далее идут остальные строки. В этом примере я попытался сделать это по две строки для каждого типа, это может быть любое фиксированное число (3,4,5). Я считаю, что если перемещение строк можно сделать для 2 строк, делать это для 3, 4 или 5 строк должно быть одинаково. Пожалуйста, дайте мне знать, если требование не ясно. Пожалуйста, игнорируйте пробел в начале, который был добавлен при копировании/вставке. Отступ должен оставаться таким же, как и ввод.   -  person A.G.Progm.Enthusiast    schedule 16.04.2018


Ответы (3)


Я бы подошел к этому, проанализировав файл, чтобы собрать эти первые пары строк в структуре данных и отправить другие строки во временный файл. После того, как вы закончите синтаксический анализ файла, распечатайте пары строк из структуры данных в выходной файл, а затем добавьте временный файл в конец выходного файла.

образец кода:

use strict;
use warnings;
use feature ':5.16';

my $infile = 'infile';
my $outfile = 'outfile';
my $tempfile = 'temp';
my $quantity = 2;  # or whatever

open my $in, '<', $infile or die 'Could not open infile: ' . $!;
open my $out, '>', $outfile or die 'Could not create output file: ' . $!;
open my $temp, '>', $tempfile or die 'Could not create tempfile: ' . $!;

my $hash = {};
my @order;
my $hdr;

while ( <$in> ) {
  if ( $hdr ) {
    my @cols = split ",", $_;
    my $key = $cols[3];

    # have we seen this key before?
    if ( ! $hash->{$key} ) {
      push @order, $key;
      $hash->{$key} = [ $_ ];
    }
    elsif ( scalar @{$hash->{$key}} < $quantity ) {
      push @{$hash->{$key}}, $_;
    }
    else {
      print { $temp } $_;
    }
  }
  else {
    # the header line
    print { $out } $_;
    $hdr = $_;
  }
}

# print the collected twofers out into the tempfile
for my $key ( @order ) {
  print { $out } @{$hash->{$key}};
}
close $out;
close $temp;

# concatenate the files
system join ' ', ( 'cat', $tempfile, '>>', $outfile );

Если парные строки не обязательно должны быть в том порядке, в котором они появляются в исходном файле, вы можете пропустить @order материал.

person i alarmed alien    schedule 16.04.2018
comment
@i встревоженный инопланетянин, мы не можем сделать это, не открывая несколько файлов. В том же файле можно ли перемещать строки вверх и вниз? Если да, пожалуйста, дайте мне знать или укажите мне что-то, что показывает, как это сделать. Спасибо за сообщение. - person A.G.Progm.Enthusiast; 17.04.2018
comment
Да, возможно, но IMO намного проще создать временный файл, а затем удалить его. Вы также можете делать все это в памяти — вместо того, чтобы печатать во временный файл, поместите строки в массив, который вы распечатаете после того, как просмотрите входной файл и найдете первые вхождения каждого типа. Есть много разных способов справиться с этой задачей; Я не сторонник перезаписи файлов на месте, так как если что-то пойдет не так, вы потеряете исходный файл. Создание нового выходного файла оставляет ваш входной файл нетронутым, если его нужно использовать для чего-то другого (или если вы найдете ошибку в своем коде!). - person i alarmed alien; 17.04.2018

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

use strict;
use warnings;
use feature 'say';
my $syntax = [];
my $NUM = 2;   # change number if needed
my $filename = 'file.txt';
my $data = {};  # make a hash of data

open(my $fh, '<:encoding(UTF-8)', $filename)
or die "Could not open file '$filename' $!";
while (my $row = <$fh>) {
    chomp $row;
    next if $. == 1; # skip header row
    my @columns = split (',', $row);

    push @$syntax, $columns[3];   # make a list of all syntaxes available
    push @{$data->{$columns[3]}}, $row;
}
close $fh;

my $processed = {};
# loop throught the syntax array and print data from hash
# also, make a counter of the number of times that syntax is used.
# it will help us to skip next (n-1) occurence of that syntax
for my $syntax (@$syntax) {
    if (!$processed->{$syntax}){
        for my $s (splice @{$data->{$syntax}}, 0, $NUM) {
            $processed->{$syntax} += 1;
            say $s;
        }
    } else {
        $processed->{$syntax} -= 1;
    }
}
# print out the remaining values
for my $rem (values %$data){
    say for @$rem;    
}

Выход:

a,b,c,perl,d
a,e,c,perl,d
a,w,c33,java,d
wa,b,c33,java,d
a,s,c,python,d1
a,f,c,python,dd
a,n,c,php,d3
wa,b,c33,php,d
d,r,hhh,cpp,d0
d,buuu,hhh,cpp,d0
d,m,hhh,c#,d0
wa,b,c33,c#,d
a,o,c,pdf,d3
a,yb,c,c,ddf
a,h,c,perl,dg
a,b,c,perl,dt
d44,b,hhh,nlp,d0
a,be,c,js,d4
wa,b,c33,python,d
wa,b,c33,python,d
wa,b,c33,python,d
wa,b,c33,python,d
wa,b,c33,php,d
wa,b,c33,php,d
wa,b,c33,java,d
wa,b,c33,java,d
wa,b,c33,python,d
wa,b,c33,python,d
wa,b,c33,perl,d
person Kamal Nayan    schedule 16.04.2018
comment
Я думаю, что ваш вывод немного отличается. Поскольку «nlp» и «js» также являются другим типом «синтаксиса» (хотя они все же встречаются только один раз), поэтому их нужно было переместить вверх после строки «a, yb, c, c, ddf». Остальная часть строки должна быть написана в соответствии с исходной последовательностью, они не должны быть записаны в соответствии с последовательностью одного и того же синтаксиса один за другим. Наконец, строки должны быть перемещены вверх в том же файле вместо того, чтобы печатать их на терминале, и первая строка с именами столбцов также должна быть в файле csv как есть. - person A.G.Progm.Enthusiast; 16.04.2018
comment
Я сделал это на основе последовательности на входе, скажем, perl, java, perl, python, perl, python, perl, nlp, python, python, тогда в соответствии с последовательностью это будет perl, perl, java, perl (skip), python, python, perl, perl, python (skip), perl (skip), nlp, python, python, т.е. perl, perl, java, python, python, perl, perl, nlp, python, python. Пожалуйста, поправьте, если я что-то неправильно понял. - person Kamal Nayan; 16.04.2018
comment
А для записи в тот же файл прочитайте весь файл в массиве @data = <$fh>, переберите его, а затем откройте тот же файл в режиме записи. Я бы предложил писать в отдельный файл, а не читать весь файл в массиве, если только это не требуется. Я придумаю любой более эффективный способ, если что-то придет мне в голову. - person Kamal Nayan; 16.04.2018
comment
ну, в этом комментарии приведенный вами пример последовательности верен, но я не уверен, генерирует ли ваш код эту последовательность. Неважно. Я посмотрю, смогу ли я это сделать. Да, эффективность мне нужна, потому что это огромный текстовый файл, поэтому анализ всего массива или использование другого файла - это не то, что мне нужно. Спасибо. - person A.G.Progm.Enthusiast; 16.04.2018
comment
Мой код работает неправильно, так что проблема. Я включил ожидаемый результат, который мой код не может произвести. - person A.G.Progm.Enthusiast; 16.04.2018

У меня есть огромный файл CSV почти из 20 тыс. строк в следующем формате:

Это не так уж и много. Размер файла, вероятно, около мегабайта.

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

Если я вас правильно понял, ваша проблема может быть решена быстро (в программистское время), потратив немного памяти:

#!/usr/bin/env perl

use strict;
use warnings;
use List::Util qw( uniqstr );

my $TOP = 2;

(my $header = <DATA>) =~ s/\s+\z//;
my @header = split /,|\s+/, $header;
my %idx = map +($header[$_] => $_), 0 .. $#header;

my @lines = grep /\S/, <DATA>;
my %syntax_of = map +($_ => (split /,/, $_)[$idx{syntax}]), @lines;

my @syntaxes = uniqstr map $syntax_of{$_}, @lines;

my %lines_of;
for my $n (0 .. $#lines) {
    push @{$lines_of{$syntax_of{$lines[$n]}}}, $n;
}

print "$header\n";

for my $syntax (@syntaxes) {
    my @top = grep defined, map $lines_of{$syntax}->[$_ - 1], 1 .. $TOP;
    print @lines[@top];
    # normally, invoking delete on an array slice is not
    # but it is just what we need here.
    delete @lines[@top];
}

print grep defined, @lines;

__DATA__
file,tools,edit,syntax,buffers
a,b,c,perl,d
a,w,c33,java,d
a,e,c,perl,d
a,s,c,python,d1
a,n,c,php,d3
d,r,hhh,cpp,d0
d,m,hhh,c#,d0
a,o,c,pdf,d3
a,f,c,python,dd
a,h,c,perl,dg
a,yb,c,c,ddf
a,b,c,perl,dt
wa,b,c33,java,d
d,buuu,hhh,cpp,d0
d44,b,hhh,nlp,d0
a,be,c,js,d4
wa,b,c33,java,d
wa,b,c33,python,d
wa,b,c33,python,d
wa,b,c33,c#,d
wa,b,c33,python,d
wa,b,c33,php,d
wa,b,c33,python,d
wa,b,c33,php,d
wa,b,c33,python,d
wa,b,c33,perl,d
wa,b,c33,php,d
wa,b,c33,java,d
wa,b,c33,python,d

PS: См. также Tie::File.

PPS: На первый взгляд, есть по крайней мере шесть вещей, которые можно было бы настроить здесь, если бы вы хотели потратить на это время.

person Sinan Ünür    schedule 16.04.2018
comment
%idx = map +($header[$_] =› $_), 0 .. $#hdr , я понял, что он делает, но я больше знаком с map {$hash{$_}=› 'что-то' } Синтаксис типа @arr. Если бы вы объяснили, что такое map+(), было бы здорово. uniqstr мне недоступен в List::Util, поэтому пришлось делать это с помощью хеша. Может быть, 20 000 000 000 000 000 000 000 — это не так уж и много, но может дойти до 2000 000, поэтому мне нужен оптимизированный способ. Вы не показали, как писать в файл, нужно ли это делать с помощью tie::file? или я могу просто напечатать эти строки в другом файле. Но не уверен насчет части оптимизации. Наконец, спасибо за код. - person A.G.Progm.Enthusiast; 17.04.2018
comment
Возможно, 20 000 — это не так уж и много, но может дойти до 2000 000 000 000 000 000 000 000 000 ... Теперь вы меняете параметры задачи. Тем не менее 2 миллиона строк занимают всего около 160 МБ, но все равно не так уж и много. Если у вас было 160 ГБ, то вы получаете большие размеры файлов. Компромисс между несколькими проходами по файлу и массиву не очевиден, и вам придется измерять, чтобы увидеть, какой из них работает лучше. Я использовал uniquestr, потому что он сохраняет порядок появления в файле данных (что, казалось, было одним из ваших критериев), можно было бы также использовать Tie::IxHash. map expr, list быстрее, чем map block list. - person Sinan Ünür; 17.04.2018
comment
Если я использую Tie::File и сначала читаю весь файл в @arr, то я использую @arr вместо @lines в строке my %syntax_of = map +($_ => (split /,/, $_)[$idx{syntax}]), @lines;. В этом случае он снова разделяет заголовок и любую пустую строку. Поскольку вы сначала читаете заголовок, а остальные строки как my @lines = grep /\S/, <DATA>;, значит, он работает на вас. Как мне позаботиться об этом, используя Tie::File. Или вместо использования map я делаю их с помощью цикла и продолжаю делать дальше, когда получаю заголовки или пустые строки? - person A.G.Progm.Enthusiast; 18.04.2018