Регулярное совпадение или одно или оба, но не дважды - PullRequest
0 голосов
/ 11 марта 2020

Я изо всех сил пытаюсь подумать, как бы я go написал о регулярном выражении, которое соответствовало бы любому из них:

pirates
scallywags
pirates scallywags
scallywags pirates

Но ни одному из них:

pirates pirates
scallywags scallywags
pirates booty scallywags
booty pirates

Конечно, я мог бы перечислить все возможные перестановки в качестве альтернатив:

(pirates|scallywags|pirates scallywags|scallywags pirates)

Но я чувствую, что должен быть более простой / более эффективный способ.

Ответы [ 4 ]

3 голосов
/ 11 марта 2020

Если у вас есть только два слова, то у вас уже есть лучшее решение (за исключением ненужного захвата и отсутствующих якорей).

Если у вас есть больше слов, то механизм регулярных выражений не ваш лучший вариант .


Самый эффективный подход на основе регулярных выражений - это тот, который у вас есть:

$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";

Хорошо, так что это не стоит двух слов, но что, если их 5? Создание 31 строки вручную было бы очень подвержено ошибкам. Приведенный выше код создаст эти 31 строку, а механизм регулярных выражений Perl создаст из них эффективный tr ie.

Но действительно ли использование механизма регулярных выражений является лучшим вариантом в этой точке? Давайте поработаем с подсчитанным множеством.

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 голосов
/ 11 марта 2020

Все еще недостаточно умен, но будет работать:

^(pirates|scallywags)(?! \1)( (pirates|scallywags))?$
2 голосов
/ 11 марта 2020

[ Когда я писал это, я представлял, что могут быть другие слова до, после и между интересующими словами. Но это не то, что вы спросили. Я оставлю здесь ответ на всякий случай, если кто-то посчитает его полезным. ]

Наиболее удобно использовать несколько совпадений.

/\b(?:pirates|scallywags)\b/
&& !/\b booty \b/x &&
&& !/\b(pirates|scallywags)\b .* \b\1\b/xs

Использование только двух уже влияет на читаемость.

/\b(?:pirates|scallywags)\b/
&& !/ \b (?: booty | (pirates|scallywags)\b .* \b\1 ) \b/xs

Это можно сделать, используя один.

/
   ^
   (?! .* \b (?: booty | (pirates|scallywags)\b .* \b\1 ) \b )
   .* \b(?:pirates|scallywags)\b
/xs

Если вы хотите избежать сканирования строки дважды, вы можете использовать следующее:

/
   ^
   (?:(?! \b(?:booty|pirates|scallywags)\b ).)*
   \b(?:pirates|scallywags)\b
   (?:(?! \b(?:booty|pirates|scallywags)\b ).)*
   \z
/xs

Он оказывается достаточно читабельным для тех, кто знаком с идиомой (?:(?!PATTERN).)*.

Какой из этих трех параметров наиболее быстрый, может зависеть от длины искомых строк, от того, как часто они содержат pirates или scallywags как часто они содержат booty, и как близко к их началу pirates или scallywags обычно находится, когда это так.

1 голос
/ 11 марта 2020

Возможное решение, но, вероятно, далеко от лучшего (отрицательное совпадение)

use strict;
use warnings;
use feature 'say';

my $re = qr/\b(pirates|scallywags)\b\s+\1|\bbooty\b/;

while(<DATA>) {
    chomp;
    say if $_ !~ /$re/;
}


__DATA__
pirates
scallywags
pirates scallywags
scallywags pirates
pirates pirates
scallywags scallywags
pirates booty scallywags
booty pirates

Вывод

pirates
scallywags
pirates scallywags
scallywags pirates
...