2010-11-04 8 views
1

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

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 и т. Д. Мне также может потребоваться выбрать на основе значения в этих свойствах, поэтому, например, только те блоки, у которых есть свойство 3, и его значение равно ' Ань.

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

+3

Можете ли вы опубликовать код, который у вас есть до сих пор, чтобы мы могли видеть, что вы пытаетесь? –

ответ

13

Секрет, чтобы сделать эту задачу простой, чтобы использовать $/переменную положить 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 
+0

@ davorg thanx много, это действительно работает. но я заметил, что в конечном массиве порядок не поддерживается, как в исходных данных, но это не проблема, но задается вопросом, может ли это быть сохранено и в некотором роде. – sfactor

+0

@sfactor: Мой скрипт передает ваши данные в список, который сохраняет заказ. –

+0

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

3

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

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

2
#!/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. 
    } 
} 
+0

Мне это нравится. Было бы просто добавить в CLI, чтобы указать свойства и значения, которые будут соответствовать. Хороший и чистый. – Sorpigal

2

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

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

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

$/ = "\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 

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

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

my(@lines) = <DATA>

+4

Несколько комментариев здесь. Это более безопасно, чем «local», иначе «$ /», а не глобально. И так называемый режим «абзаца» slurp можно включить, установив '$ /' to ''''. – Zaid

0

Если предположить, что ваши данные сохраняются в файл (скажем mydata.txt), вы могли бы написать следующий сценарий PERL (давайте назовем его Бобом.пл):

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; 

Далее, вы просто должны Lauch perl Bob.pl < mydata.txt, и вуаля!

localhost> perl Bob.pl < mydata.txt 
Property 1: just 
Property 3: an 
Property 5: simple 
Property 6: example 
+4

Никогда, никогда, никогда не пишите '$ _ = ~ /.../' !! – tchrist

1

Ваш разделитель записей должен быть "\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 
     ) { 
     ... 
    } 
} 

Оба выражения используют многострочные («м») флаг, так что ^ относится к любой начальной линии. Последний использует флаг, чтобы включить символы новой строки в '.' выражения ('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

По отношению к первой части вашего вопрос, вы можете прочитать записи в «paragraph mode» с использованием опции командной строки perl -00, например:

#!/usr/bin/perl -00 

my @data = <>; 

# Print the last block. 
print $data[-1], "\n"