1

我有一些数据需要分析。数据是多行的,每个块由换行符分隔。所以,它是这样的

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”,但似乎无法正常工作。我错过了什么吗?

4

8 回答 8

14

使这项任务变得简单的秘诀是使用 $/ 变量将 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
于 2010-11-04T09:28:52.600 回答
3

取决于每个属性集的大小和你有多少内存......

我会使用一个简单的状态机来按顺序扫描文件 - 使用逐行顺序扫描,而不是多行 - 将每个属性/id/值添加到以 id 为键的哈希中。当你得到一个空行或文件结尾时,确定是否应该过滤散列的元素,并在必要时发出它们,然后重置散列。

于 2010-11-04T09:07:34.263 回答
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.
    }
}
于 2010-11-04T09:09:23.797 回答
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";
于 2010-11-04T09:11:37.180 回答
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 
       ) {
        ...
    }
}

两个表达式都使用多行 ('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'
           }
         );
于 2010-11-04T15:09:50.867 回答
0

检查 $/ 变量将为您做什么,例如此处的说明。您可以将“行尾”分隔符设置为您喜欢的任何内容。您可以尝试将其设置为 '\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>

于 2010-11-04T09:18:04.227 回答
0

假设您的数据存储在一个文件中(比如说 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;

接下来,你只需要 lauch perl Bob.pl < mydata.txt,瞧!

localhost> perl Bob.pl < mydata.txt
Property 1: just
Property 3: an
Property 5: simple
Property 6: example
于 2010-11-04T10:42:10.177 回答
0

关于问题的第一部分,您可以使用 perl 的命令行选项以“段落模式-00”读取记录,例如:

#!/usr/bin/perl -00

my @data = <>;

# Print the last block.
print $data[-1], "\n"
于 2012-01-30T10:25:07.523 回答