Regex 为什么我的Perl正则表达式这么慢?
我有以下正则表达式:Regex 为什么我的Perl正则表达式这么慢?,regex,perl,Regex,Perl,我有以下正则表达式: my $scores_compiled_regex = qr{^0 \s+ (\p{Alpha}+\d*) \s+ (\d+ \s*
my $scores_compiled_regex = qr{^0
\s+
(\p{Alpha}+\d*)
\s+
(\d+
\s*
\p{Alpha}*)
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s{2,}
(\d+)?
\s+
\d+ #$
}xos
)
它应该匹配以下行(来自普通txt文件):
而列名是:
0 INST, NAME A A- B+ B B- C+ C C- D+ D D- F CR P PR I I* W WP WF AU NR FN FS
这意味着:得分A=1,得分A-=1,没有得分B+,得分B=5,等等。。
我试着把它分割成一个列表,不忽略空列,它工作,但是非常慢,匹配也非常慢,我的意思是,超过5秒,有时甚至更多
文件中的前几个文件如下所示:
0 PALMER, JAN A A- B+ B B- C+ C C- D+ D D- F CR P PR I I* W WP WF AU NR FN FS TOTAL
0 ECON 103 98 35 114 1 14 75 9 35 1 10 1
分数是右边A列后面的任何东西
有什么想法吗?
谢谢,如果您必须接受的格式与您的正则表达式当前接受的格式一样松散,那么您会遇到一个大问题:如果缺少一个或多个数字字段,并且如果一行中有多个空格出现,那么哪个分数对应于哪个列是不明确的 Perl的回溯将通过选择“最左边、最长”的匹配来解决歧义,但是(a)这不一定是您想要的,(b)它需要尝试的可能性的数量与您在行中丢失的数字字段的数量成指数关系,因此速度较慢 为了举例说明,让我们使用一个更简单的正则表达式:
/\A(\d+)?\s{2,}
(\d+)?\s{2,}
(\d+)?\s{2,}
(\d+)?\z/xs;
假设输入为:
123 456 789
(每个数字之间有四个空格。)现在,456应该是返回的第二个还是第三个字段?两者都是有效匹配项。在本例中,Perl的回溯将使其成为第二个字段,但我怀疑您是否真的想依靠Perl的回溯来决定这一点
建议:如果可能的话,将每个\s{2,}
替换为与正则表达式匹配的固定大小的空间。如果只允许其大小可变,因为数字在列中排列,并且数字可能是1或2位,那么只需使用substr()
从已知的列偏移量而不是正则表达式中获取。(使用正则表达式无法有效解析固定宽度数据。)请参阅我的:
它用于根据字符串中字段的宽度(以字符为单位)将文本拆分为多个块。有关如何使用解包进行这种字符串搜索的更多详细信息,请参见。Unpack可能是这种格式的最佳选择,因为它的执行速度确实比正则表达式快得惊人(在我的机器上,它在6秒钟内解析了600_000个这样的字符串)
请让我知道,如果你需要通过该计划的任何部分走。我没有把它贴在这里,因为它有点长(有评论总比没有好!)。请告诉我您是否希望我这样做。如果列可以为空,则可能是(a)您的数据不明确,并且您遇到了比慢速正则表达式更大的问题,或者(b)您的数据采用固定宽度格式,如下所示:
NAME A A-
foo 123 456
bar 789
fubb 111
如果您有固定宽度的数据,则适当的解析工具是(或),而不是正则表达式。不要为此使用正则表达式。它看起来像一个固定的列格式,所以速度会快得多 下面是一个示例程序,它显示了问题的实质。您仍然需要弄清楚如何集成它,以便知道新的个人记录何时开始等等。我这样做是为了使解包值的格式主要来自标题,这样您就不必花费太多时间计算列(同时也便于它对列位置的更改做出响应): 对于“Palmer,Jan”的新样品:
首先将队列分成固定宽度的块、空格和所有。然后把这些碎片清理干净。否则,您试图同时做两件容易出错的事情。是的,但它工作(慢)-:),我看不到其他方法。您尝试过拆分吗?或者将解析器生成器用作Parse::RecDescent?拆分没有帮助,即使列为空,我也需要保留它们。我认为您应该使用
(\d*)
,而不是(\d+)
。您的示例列似乎与显示的列标题不一致。我把它读作A=211,B+=1,B=1,C+=5,C-=2,D+=6,等等。不含糊:数据看起来就像是列表,类似于使用printf时的输出方式(“%-10s%-5s..,$A,$B);好的,这是个好消息!一定要删除正则表达式并使用普通的oldsubstr()
或unpack()
。它适用于您的示例,但是当我尝试我的示例时,我没有得到想要的结果,谢谢,您好,请查看我添加到原始帖子中的文件示例。谢谢,用标题替换我的示例中的标题,并根据每列应包含的字符数为数据创建正确的解包字符串。您在问题上发布的数据没有正确对齐。如果你可以发布一个离散的数据样本(带列),我可以重做这个示例,这样它就可以处理你的数据。用你在问题中输入的新数据更新你的解决方案效果很好,只有当我有3位数的数字时它才会崩溃,我尝试改为A5,这会产生更多的问题。我猜这就是你所说的,固定宽度,我将文件中的前几行添加到我的原始帖子中。使用上面给出的示例,仍然无法继续,有时某些列有3位数字,这是导致其失败的原因。如果您显示解包代码和更多示例案例,我们可能会提供帮助。上面由mfontani解包的代码,我的示例案例在我的帖子中,刚刚加上去的。顺便说一句,我喜欢你的书(但要读完它,还需要一些时间)。谢谢,谢谢!从这个回答中,我得到了肯定的教育!
use strict;
use warnings;
# Column details and sample line, from the post
my $header = q{0 AOZSVIN, TAMSSZ B A A- B+ B B- C+ C C- D+ D D- F CR P PR I I* W WP WF AU NR FN FS};
my $sample = q{0 AAS 150 23 25 16 35 45 14 8 10 2 1 1 4 4 };
# -+--------+-----+-----+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---..
# chars 1212345678912345612345612341234123412341234123412341234123412341234123412341234123412341234123412341234123412341234...
# num. chars: 2 9 6 6 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 *
my $unpack = q{A2A9 A6 A6 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A4 A*};
$unpack =~ s/\s//g;
# Get column names from the "$header" variable above
my @column_names = unpack($unpack, $header);
s/\s+$// for @column_names; # get rid of trailing spaces
s/^\s+// for @column_names; # get rid of leading spaces
# Some sample data in same format, to try the script out
my @samples = (
q{0 AAS 150 23 25 16 35 45 14 8 10 2 1 1 4 4 },
q{0 AAS 353 2 3 5 2 6 1 2 },
q{0 T304 480M 3 10 8 8 2 3 2 1 1 1 },
q{0 BIOS 206 3 14 5 11 9 8 4 8 3 1 1 6 7 },
);
my @big_sample = (@samples) ;#x 200_000;
my @unpacked_data_as_arrayrefs;
m y @unpacked_data_as_hashrefs;
my $begin = time;
for my $line ( @big_sample ) {
my @data = unpack($unpack,$line);
s/\s+$// for @data; # get rid of trailing spaces
s/^\s+// for @data; # get rid of leading spaces
push @unpacked_data_as_arrayrefs, [@data]; # stop here if this is all you need
## below converts the data in a hash, based on the column names given
#my %as_hash;
#for ( 0..$#column_names ) {
# $as_hash{ $column_names[$_] } = $data[$_];
#}
#push @unpacked_data_as_hashrefs, { %as_hash };
}
my $tot = time - $begin;
print "Done in $tot seconds\n";
# verify all data is as we expected
# uncomment the ones that test hashref, if the above hashref-building code is also uncommented.
{
use Test::More;
# first sample
is($unpacked_data_as_arrayrefs[0]->[2],'AAS'); # AAS in the third column
is($unpacked_data_as_arrayrefs[0]->[7],'35'); # 35 in the 8th column
# fourth sample
is($unpacked_data_as_arrayrefs[3]->[2],'BIOS');
is($unpacked_data_as_arrayrefs[3]->[15],'6');
# sixth
is($unpacked_data_as_arrayrefs[5]->[7],'114');
is($unpacked_data_as_arrayrefs[5]->[10],'75');
done_testing();
}
NAME A A-
foo 123 456
bar 789
fubb 111
chomp( my $header = <DATA> );
my( $num, $name, $rest ) = unpack "a2 a20 a*", $header;
my @grades = split /(?=\s+)/, $rest;
my @grade_keys = map { /(\S+)/} @grades;
my $format = 'a13 a4 a5 ' . join ' ', map { 'a' . length } @grades;
while( <DATA> ) {
my( $key, $label, $number, @grades ) = unpack $format, $_;
$$_ =~ s/\s//g foreach ( \$key, \$label, \$number );
@{ $hash{$key}{$label}{$number} }{@grade_keys} =
map { s/\s//g; $_ } @grades;
}
use Data::Dumper;
print Dumper( \%hash );
$VAR1 = {
'0' => {
'BIOS' => {
'206' => {
'F' => '6',
'AU' => '',
'FS' => '',
'B-' => '9',
'D+' => '3',
'CR' => '',
'B+' => '5',
'WP' => '7',
'C+' => '8',
'NR' => '',
'C' => '4',
'PR' => '',
'A' => '3',
'W' => '',
'I*' => '',
'A-' => '14',
'P' => '',
'WF' => '',
'B' => '11',
'FN' => '',
'D' => '1',
'D-' => '1',
'I' => '',
'C-' => '8'
}
},
'AAS' => {
'353' => {
'F' => '2',
'AU' => '',
'FS' => '',
'B-' => '6',
'D+' => '',
'CR' => '',
'B+' => '5',
'WP' => '',
'C+' => '',
'NR' => '',
'C' => '1',
'PR' => '',
'A' => '2',
'W' => '',
'I*' => '',
'A-' => '3',
'P' => '',
'WF' => '',
'B' => '2',
'FN' => '',
'D' => '',
'D-' => '',
'I' => '',
'C-' => ''
},
'150' => {
'F' => '4',
'AU' => '',
'FS' => '',
'B-' => '45',
'D+' => '2',
'CR' => '',
'B+' => '16',
'WP' => '4',
'C+' => '14',
'NR' => '',
'C' => '8',
'PR' => '',
'A' => '23',
'W' => '',
'I*' => '',
'A-' => '25',
'P' => '',
'WF' => '',
'B' => '35',
'FN' => '',
'D' => '1',
'D-' => '1',
'I' => '',
'C-' => '10'
}
},
'T304' => {
'480M' => {
'F' => '',
'AU' => '',
'FS' => '1',
'B-' => '2',
'D+' => '',
'CR' => '',
'B+' => '8',
'WP' => '',
'C+' => '3',
'NR' => '',
'C' => '2',
'PR' => '',
'A' => '3',
'W' => '',
'I*' => '',
'A-' => '10',
'P' => '',
'WF' => '1',
'B' => '8',
'FN' => '',
'D' => '',
'D-' => '',
'I' => '',
'C-' => '1'
}
}
}
};
$VAR1 = {
'0' => {
'ECON' => {
'103' => {
'F' => '35',
'AU' => '1',
'FS' => '',
'B-' => '1',
'D+' => '',
'CR' => '',
'B+' => '35',
'WP' => '10',
'C+' => '14',
'NR' => '',
'C' => '75',
'PR' => '',
'A' => '98',
'W' => '',
'I*' => '',
'A-' => '',
'P' => '',
'WF' => '',
'B' => '114',
'FN' => '',
'TOTAL' => '',
'D' => '9',
'D-' => '',
'I' => '1',
'C-' => ''
}
}
}
};