Если у вас есть только два слова, то у вас уже есть лучшее решение (за исключением ненужного захвата и отсутствующих якорей).
Если у вас есть больше слов, то механизм регулярных выражений не ваш лучший вариант .
Самый эффективный подход на основе регулярных выражений - это тот, который у вас есть:
$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";