Perl正则表达式替代哈希

时间:2011-06-21 19:34:22

标签: regex perl

是否有一种使用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中完成上述操作?

7 个答案:

答案 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行):

  1. $regex{$1}方法需要 6秒(使用/ go代替/ g需要5秒)
  2. tie方法需要 10秒
  3. OP方法略低于1秒(使用/ go代替/ g)
  4. eval方法不到1秒(比OP代码更快)
  5. 这是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修饰符。

请注意,如果有一些键是另一个键的前缀(例如ffoo),则在连接的正则表达式中以先到者为准将被视为匹配(例如{{1}匹配f|foof匹配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;