Regex匹配一个或两个,但不匹配两次

问题描述 投票:0回答:4

我正在努力思考如何编写与以下任何一种匹配的正则表达式:

pirates
scallywags
pirates scallywags
scallywags pirates

但这些都不是:

pirates pirates
scallywags scallywags
pirates booty scallywags
booty pirates

当然,我可以列出所有可能的排列作为替代:

(pirates|scallywags|pirates scallywags|scallywags pirates)

但是我觉得应该有一种更容易/更有效的方法。

regex perl pcre
4个回答
2
投票

仍然不够聪明,但是会起作用:

^(pirates|scallywags)(?! \1)( (pirates|scallywags))?$

3
投票

如果只有两个单词,那么您已经有了最佳解决方案(除了不必要的捕获和锚点丢失之外。)>

如果您有更多的单词,那么正则表达式引擎不是您的最佳选择。


最有效的基于正则表达式的方法是您拥有的方法:

$str =~ /^(?:pirates|scallywags|pirates scallywags|scallywags pirates)\z/

缺点是代码重复。通过动态构建模式来保持最大效率的同时可以避免这种情况。

use Math::Combinatorics qw( );

sub build_re {
   my @quoted = map quotemeta, @words;
   my @alts;
   for my $r (1..$#words) {
      my $mc = Math::Combinatorics->new( count => $r, data => \@quoted );
      while ( my @combo = $mc->next_combination ) {
         push @alts, join " ", @combo;
      }
   }

   my $alt = join "|", @alts;
   return qr/^(?:$alt)\z/;
}

my @words = qw( pirates scallywags );
my $re = build_re(\@words, $re);

$str =~ $re
   or die "Invalid\n";

好,所以两个字不值得,但是如果有五个字呢?手动创建31个字符串很容易出错。上面的代码将创建这31个字符串,而Perl regex引擎将根据它们创建有效的trie。

但是那时候使用正则表达式引擎确实是最好的选择吗?让我们使用一个计数集代替。

sub check {
   my $words = shift;

   my %counts;
   ++$counts{$_} for split ' ', $_[0];

   my $any;
   for (@words) {
      my $count = delete($counts{$word})
         or next;

      return 0 if $count > 1;
      ++$any;
   }

   return $any && !%counts;
}

my @words = qw( pirates scallywags );
check(\@words, $str)
   or die "Invalid\n";

2
投票

[当我写这篇文章时,我想在感兴趣的单词之前,之后和之间可能会有其他单词。但这不是你要的。如果有人觉得有用,我将在此处保留答案。


1
投票

可能的解决方案,但可能与最佳方案(否定匹配)相去甚远

© www.soinside.com 2019 - 2024. All rights reserved.