是否有一种使用Perl哈希值替换字符串的有效方法?
例如,
$regex{foo} = "bar";
$regex{hello} = "world";
$regex{python} = "perl";
open(F, "myfile.txt");
while (<F>) {
foreach $key (keys %regex) {
s/$key/$regex{$key}/g;
}
}
close(F);
有没有办法在Perl中完成上述操作?
答案 0 :(得分:5)
第一个问题:你确定你所拥有的效率低下?
其次,最明显的下一步是将所有内容整合到一个正则表达式中:
my $check = join '|', keys %regex;
然后你可以替换为:
s/($check)/$regex{$1}/g;
这仍然是“慢”的,因为正则表达式引擎必须不断地重新检查相同的字母。您可以使用类似Regexp::Optimizer的内容来消除重叠。但是优化的成本可能不仅仅是执行所有操作的成本,具体取决于更改的数量(哈希中的键/值)以及您要修改的行数。过早优化 - !
请注意,当然,您的示例代码在替换后没有对文本执行任何操作。它不会就地修改文件,所以我假设您正在单独处理它。
答案 1 :(得分:4)
为了证明eval
的重点并且出于好奇,我使用OP的代码与$regex{$1}
方法与eval
方法进行了一些测试。
首先,在(token|token|...)
匹配表达式中填充每个可能的标记似乎没什么价值。 Perl需要立即检查所有令牌 - 这是有争议的,而不是简单地一次检查每个令牌并用硬编码值进行替换。
其次,执行$regex{$1}
表示在每次匹配时都会提取hashmap键。
无论如何,这里有一些数字(在草莓5.12上运行,带有4MB文件的100K行):
$regex{$1}
方法需要 6秒(使用/ go代替/ g需要5秒)tie
方法需要 10秒 eval
方法不到1秒(比OP代码更快)这是eval
方法:
$regex{foo} = "bar";
$regex{hello} = "world";
$regex{python} = "perl";
$regex{bartender} = "barista";
$s = <<HEADER;
\$start = time;
open(F, "myfile.txt");
while (<F>) {
HEADER
foreach $key (keys %regex) {
$s .= "s/$key/$regex{$key}\/go;\n"
}
$s .= <<FOOTER;
print \$_;
}
close(F);
print STDERR "Elapsed time (eval.pl): " . (time - \$start) . "\r\n";
FOOTER
eval $s;
答案 2 :(得分:3)
定义与任何键匹配的正则表达式。
$regex = join("|", map {quotemeta} keys %regex);
将$regex
的所有匹配项替换为$regex{$1}
。
s/($regex)/$regex{$1}/go;
如果在执行程序期间o
发生更改,则忽略$regex
修饰符。
请注意,如果有一些键是另一个键的前缀(例如f
和foo
),则在连接的正则表达式中以先到者为准将被视为匹配(例如{{1}匹配f|foo
但f
匹配foo|f
中的foo
。如果发生这种情况,您可能需要根据您想要获胜的匹配对foobar
进行排序。 (感谢ysth指出这一点。)
答案 3 :(得分:1)
perl -e ' \
my %replace = (foo=>bar, hello=>world, python=>perl); \
my $find = join "|", sort keys %replace; \
my $str = "foo,hello,python"; \
$str =~ s/($find)/$replace{$1}/g; \
print "$str\n\n"; \
'
您可能想要考虑的事情不是逐行处理文件,而是一次处理整个文件并使用正则表达式上的/s
修饰符进行单行模式。
答案 4 :(得分:1)
你的工作原理是什么,因此不清楚你的要求是什么。
一个问题:您发布的代码可能会遇到双重替换问题,具体取决于%regex
和/或$_
的内容。例如,
my %regex = (
foo => 'bar',
bar => 'foo',
);
解决方案是将foreach移动到模式中,可以这么说。
my $pat =
join '|',
map quotemeta, # Convert text to regex patterns.
keys %regex;
my $re = qr/$pat/; # Precompile for efficiency.
my $qfn = 'myfile.txt'
open(my $fh, '<', $qfn) or die "open: $qfn: $!";
while (<$fh>) {
s/($re)/$regex{$1}/g;
... do something with $_ ...
}
答案 5 :(得分:1)
#!/usr/bin/perl
use strict;
use Tie::File;
my %tr=( 'foo' => 'bar',
#(...)
);
my $r =join("|", map {quotemeta} keys %tr);
$r=qr|$r|;
使用大文件的tie my @array,"Tie::File",$ARGV[0] || die;
for (@array) {
s/($r)/$tr{$1}/g;
}
untie @array;
使用小文件的open my $fh,'<',$ARGV[0] || die;
local $/ = undef;
my $t=<$fh>;
close $fh;
$t=~s/($r)/$tr{$1}/g;
open $fh,'>',$ARGV[0] || die;
print $fh $t;
close $fh;
答案 6 :(得分:0)
这是一个老问题,所以我很惊讶没有人提出明显的建议:预编译每个正则表达式(即散列键)。
$regex{qr/foo/} = 'bar';
$regex{qr/hello/} = 'world';
$regex{qr/python/} = 'perl';
open(F, "myfile.txt");
while (<F>) {
foreach $key (keys %regex) {
s/$key/$regex{$key}/g;
}
}
close(F);
或(IMO)更高的可读性:
%regex = (
qr/foo/ => 'bar',
qr/hello/ => 'world',
qr/python/ => 'perl',
);
如果你知道每个输入行只能有一个匹配,那么在成功匹配后用last
跳过剩余的正则表达式也会有很多帮助。例如在for
循环中:
s/$key/$regex{$key}/g && last;