如何将正则表达式中的变量插值延迟到使用点?

Mic*_*eyn 9 regex perl

例如,让我们假设我有一组变量和一组插入这些变量的正则表达式:

my ($var1, $var2, $var3);
my @search_regexes=(
  qr/foo $var1/,
  qr/foo bar $var2/,
  qr/foo bar baz $var3/,
);
Run Code Online (Sandbox Code Playgroud)

上面的代码将给我们警告告诉我们$var1,$var2并且$var3没有在正则表达式编译中为正则表达式定义$search_regexes.但是,我想在这些正则表达式中延迟变量插值,直到实际使用它们为止(或者在变量具有值后稍后(重新)编译):

# Later on we assign a value to $var1 and search for the first regex in $_ ...
$var1='Hello';
if (/$search_regexes[0]/)
{
  # Do something ...
}
Run Code Online (Sandbox Code Playgroud)

我将如何在初始代码示例中重构构造以实现此目的?

作为奖励,我想在将值分配给该正则表达式中出现的相应变量之后编译每个正则表达式,其方式与qr//操作员现在所做的相同(但为时太早).如果您可以展示如何进一步扩展解决方案以实现这一点,我将非常感激.

更新:

我已经确定了Hunter方法的变体,因为使用它我不会受到性能影响,并且对现有代码的修改很少.其他答案也教会了我很多关于这个问题的替代解决方案及其在需要匹配很多行时的性能影响.我的代码现在类似于以下内容:

my ($var1, $var2, $var3);
my @search_regexes=(
  sub {qr/foo $var1/},
  sub {qr/foo bar $var2/},
  sub {qr/foo bar baz $var3/},
);

...
($var1,$var2,$var3)=qw(Hello there Mr);

my $search_regex=$search_regexes[$based_on_something]->();

while (<>)
{
  if (/$search_regex/)
  {
    # Do something ...
    # and sometimes change $search_regex to be another from the array
  }

}
Run Code Online (Sandbox Code Playgroud)

这让我得到了我正在寻找的代码,只需对我的代码进行最小的更改(例如,只是将数组添加到数组中)并且每个正则表达式的使用没有性能损失.

amo*_*mon 11

最好的解决方案是推迟正则表达式的编译,直到定义了这些变量.但首先是一个值得怀疑的解决方案:正则表达式可以包含代码:qr/foo (??{ $var1 })/.在匹配期间执行该块,然后将块的结果用作模式.

我们如何推迟编译?

  1. 通过在分配变量时简单地指定它们.这可能是您可能认为的问题,因为任何程序都可以在没有(重新)分配变量的情况下表达.坚持任何声明也必须是作业的规则(反之亦然),这应该有效.这个:

    my $var1;
    my $re = qr/$var1/;
    $var1 = ...;
    $bar =~ $re;
    
    Run Code Online (Sandbox Code Playgroud)

    变为:

    my $var1 = ...;
    $re = qr/$var1/;
    $bar =~ $re;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果这不可能,我们可能想要在匹配之前使用我们评估的闭包:

    my $var1;
    my $deferred_re = sub { qr/$var1/ };
    $var1 = ...;
    $bar =~ $deferred_re->();
    
    Run Code Online (Sandbox Code Playgroud)

    当然这会在每次调用时重新编译正则表达式.

  3. 我们可以通过缓存正则表达式扩展以前的想法:

    package DeferredRegexp;
    use overload 'qr' => sub {
      my ($self) = @_;
      return $self->[0] //= $self->[1]->();
    };
    
    sub new {
       my ($class, $callback) = @_;
       return bless [undef, $callback] => $class;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后:

    my $var1;
    my $deferred_re = DeferredRegexp->new(sub{ qr/$var1/ });
    $var1 = ...;
    $bar =~ $deferred_re;
    
    Run Code Online (Sandbox Code Playgroud)


Hun*_*len 9

我想如果你将每个正则表达式包装在匿名子中,你可以做这种推迟:

my ($var1, $var2, $var3);
my @search_regexes=(
  sub { return qr/foo $var1/         },
  sub { return qr/foo bar $var2/     },
  sub { return qr/foo bar baz $var3/ },
);
Run Code Online (Sandbox Code Playgroud)

然后,当你要评估它们时,你只需"呼叫"匿名子:

($var1, $var2, $var3) = qw(thunk this code);
if( $_ =~ $search_regexes[0]->() ) {
   # Do something
}
Run Code Online (Sandbox Code Playgroud)

我知道在Scheme中这称为thunking我不确定它是否在Perl中有一个名字.您可以在Ruby中使用Proc对象执行类似的操作

  • @MichaelGoldshteyn是的,涉及子程序会降低效率.使用面向对象(如我的答案)甚至更多.唯一的方法是以某种方式编译正则表达式,然后再匹配它们.这可以通过在任何一个匹配之前调用所有潜艇来限制,例如@ search_regexes`的`$ _ = $ _->(),它只支付性能价格一次,而不是一次又一次.更快:在分配所有涉及的变量之前不要指定正则表达式. (2认同)
  • @mpapec不是*那个*好.MJD在他的*Higher Order Perl*书中讨论了memoization,并展示了它有意义的例子(例如递归计算阶乘),以及它没有的例子(例如内置的`sqrt`).通过降低算法复杂度(参见动态编程),Memoization可以使程序更快,但在最坏的情况下(这可能是这种情况),开销比实际计算本身更重. (2认同)