Разбор многострочных данных в Perl - PullRequest
1 голос
/ 04 ноября 2010

У меня есть некоторые данные, которые мне нужно проанализировать.Данные являются многопоточными, и каждый блок отделяется новой строкой.Итак, это что-то вроде

Property 1: 1234
Property 2: 34546
Property 3: ACBGD

Property 1: 1234
Property 4: 4567

Property 1: just
Property 3: an
Property 5: simple
Property 6: example

Мне нужно отфильтровать те блоки данных, в которых присутствует какое-то конкретное свойство.Например, только те, которые имеют Свойство 4, только те, которые имеют Свойство 3 и 6 и т.д.Ань.

Как бы я это сделал в Perl.Я попытался разделить его на "\ n", но, похоже, он не работал должным образом.Я что-то упустил?

Ответы [ 8 ]

14 голосов
/ 04 ноября 2010

Секрет упрощения этой задачи заключается в использовании переменной $ / для перевода Perl в «режим абзаца».Это позволяет легко обрабатывать ваши записи по одному.Затем вы можете отфильтровать их с помощью чего-то вроде grep.

#!/usr/bin/perl

use strict;
use warnings;

my @data = do {
  local $/ = '';
  <DATA>;
};

my @with_4   = grep { /^Property 4:/m } @data;

my @with_3   = grep { /^Property 3:/m } @data;
my @with_3_6 = grep { /^Property 6:/m } @with_3;

print scalar @with_3_6;

__DATA__
Property 1: 1234
Property 2: 34546
Property 3: ACBGD

Property 1: 1234
Property 4: 4567

Property 1: just
Property 3: an
Property 5: simple
Property 6: example

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

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my @data;

{
  local $/ = '';

  while (<DATA>) {
    chomp;

    my @rec = split /\n/;
    my %prop;
    foreach my $r (@rec) {
      my ($k, $v) = split /:\s+/, $r;
      $prop{$k} = $v;
    }

    push @data, \%prop;
  }
}

my @with_4   = grep { exists $_->{'Property 4'} } @data;

my @with_3_6 = grep { exists $_->{'Property 3'} and
                      exists $_->{'Property 6'} } @data;

my @with_3an = grep { exists $_->{'Property 3'} and
                      $_->{'Property 3'} eq 'an' } @data;

print Dumper @with_3an;

__DATA__
Property 1: 1234
Property 2: 34546
Property 3: ACBGD

Property 1: 1234
Property 4: 4567

Property 1: just
Property 3: an
Property 5: simple
Property 6: example
3 голосов
/ 04 ноября 2010

Зависит от размера каждого набора свойств и от того, сколько у вас памяти ...

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

2 голосов
/ 04 ноября 2010

Быстро и грязно:

my $string = <<END;
Property 1: 1234
Property 2: 34546
Property 3: ACBGD

Property 1: 1234
Property 4: 4567

Property 1: just
Property 3: an
Property 5: simple
Property 6: example
END

my @blocks = split /\n\n/, $string;

my @desired_blocks = grep /Property 1: 1234/, @blocks;

print join("\n----\n", @desired_blocks), "\n";
2 голосов
/ 04 ноября 2010
#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

my $propertyRef;
my $propertyRefIdx = 0;

while (<>) {
    chomp($_);
    if ($_ =~ /Property (\d+): (.*)/) {
        my $propertyKey = $1;
        my $propertyValue = $2;

        $propertyRef->[$propertyRefIdx]->{$propertyKey} = $propertyValue;
    }
    else {
        $propertyRefIdx++;
    }
}

print Dumper $propertyRef;

Допустим, этот скрипт называется propertyParser.pl, и у вас есть файл со свойствами и значениями, который называется properties.txt.Вы могли бы назвать это следующим образом:

$ propertyParser.pl < properties.txt

После того, как вы заполнили $propertyRef всеми своими данными, вы можете затем циклически проходить по элементам и фильтровать их на основе любых правил, которые вам нужно применить, таких как определенный ключи / или комбинации значений:

foreach my $property (@{$propertyRef}) {
    if (defined $property->{1} && defined $property->{3} 
                               && ! defined $property->{6}) {
        # do something for keys 1 and 3 but not 6, etc.
    }
}
1 голос
/ 04 ноября 2010

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

use strict;
use warnings;
use English qw<$RS>;

open( my $inh, ... ) or die "I'm dead!";

local $RS = "\n\n";
while ( my $block = <$inh> ) { 
    if ( my ( $prop4 ) = $block =~ m/^Property 4:\s+(.*)/m ) { 
        ...
    }
    if ( my ( $prop3, $prop6 ) 
             = $block =~ m/
        ^Property \s+ 3: \s+ ([^\n]*)
        .*?
        ^Property \s+ 6: \s+ ([^\n]*)
        /smx 
       ) {
        ...
    }
}

Оба выражения используют многострочный ('m') флаг, так что ^ применяется к любому началу строки. Последний использует флаг для включения новых строк в '.' выражения ('s') и расширенный синтаксис ('x'), который, помимо прочего, игнорирует пробелы в выражении.

Если бы данные были довольно маленькими, вы могли бы обработать все это за один раз, как:

use strict;
use warnings;
use English qw<$RS>;

local $RS = "\n\n";
my @block
    = map { { m/^Property \s+ (\d+): \s+ (.*?\S) \s+/gmx } } <DATA>
    ;
print Data::Dumper->Dump( [ \@block ], [ '*block' ] ), "\n";

Который показывает результат:

@block = (
           {
             '1' => '1234',
             '3' => 'ACBGD',
             '2' => '34546'
           },
           {
             '4' => '4567',
             '1' => '1234'
           },
           {
             '6' => 'example',
             '1' => 'just',
             '3' => 'an',
             '5' => 'simple'
           }
         );
0 голосов
/ 30 января 2012

Что касается первой части вашего вопроса, вы можете читать записи в « режиме абзаца », используя параметр командной строки perl's -00, например:

#!/usr/bin/perl -00

my @data = <>;

# Print the last block.
print $data[-1], "\n"
0 голосов
/ 04 ноября 2010

Предполагая, что ваши данные хранятся в файле (скажем, mydata.txt), вы можете написать следующий скрипт на Perl (назовем его Bob.pl):

my @currentBlock = ();
my $displayCurrentBlock = 0;
# This will iterate on each line of the file
while (<>) {
  # We check the content of $_ (the current line)
  if ($_ =~ /^\s*$/) {
    # $_ is an empty line, so we display the current block if needed
    print @currentBlock if $displayCurrentBlock;
    # Current block and display status are resetted
    @currentBlock = ();
    $displayCurrentBlock = 0;
  } else{
    # $_ is not an empty line, we add it to the current block
    push @currentBlock, $_;
    # We set the display status to true if a certain condition is met
    $displayCurrentBlock = 1 if ($_ =~ /Property 3: an\s+$/);
  }
}
# A last check and print for the last block
print @currentBlock if $displayCurrentBlock;

Далее у вас просто естьperl Bob.pl < mydata.txt и вуаля!

localhost> perl Bob.pl < mydata.txt
Property 1: just
Property 3: an
Property 5: simple
Property 6: example
0 голосов
/ 04 ноября 2010

Проверьте, что $ / переменная сделает для вас, например, объяснение здесь . Вы можете установить разделитель «конец строки» так, как вам нравится. Вы можете попробовать установить его в '\ n \ n'

$/ = "\n\n";
foreach my $property (<DATA>)
    {
    print "$property\n";
    }


__DATA__
Property 1: 1234
Property 2: 34546
Property 3: ACBGD

Property 1: 1234
Property 4: 4567

Property 1: just
Property 3: an
Property 5: simple
Property 6: example

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

Вы также можете прочитать весь файл в массив и обработать его из памяти

my(@lines) = <DATA>

...