Perl-REGEXP Как сопоставить подстроку из слов без альтернативных шаблонов? - PullRequest
0 голосов
/ 11 октября 2018

Добрый день всем,

У меня есть строка пустых разделенных слов.Мне нужно найти слова из этой строки, которые соответствуют буквенно-цифровому шаблону, частичному или целому слову.Мне нужны слова, состоящие только из буквенно-цифровых символов.

Чтобы прояснить мою цель, у меня есть строка:

'foo bar quux foofoo foobar fooquux barfoo barbar barquux'.
'quuxfoo quuxbar quuxquux [foo] (foo) {foo} foofoo barfoo'.
'quuxfoo foo2foo foo2bar foo2quux foo2foo bar2foo quux2foo'

, и я хочу найти все слова с 'foo' внутри (только один раз за слово), но не те, у которых есть специальные символы (не альфа), например"[foo]", "{foo}" ...

Я сделал это со следующим фрагментом кода в Perl:

my $s=
'foo bar quux foofoo foobar fooquux barfoo barbar barquux quuxfoo quuxbar quuxquux ' .
'[foo] (foo) {foo} foofoo barfoo quuxfoo foo2foo foo2bar foo2quux foo2foo bar2foo quux2foo';
my @m = ($s=~/(\w+foo|foo\w+|^foo|foo$)/g) ;
say "@m";
say "Number of sub-strings matching the pattern: ", scalar @m;
print( sprintf("%02d: ",$_),
       ($s=~/(\w+foo|foo\w+|^foo|foo$)/g)[$_],
       qq(\n) )
    for (0..@m-1);

Я получил желаемый результат:

foo foofoo foobar fooquux barfoo quuxfoo foofoo barfoo quuxfoo foo2foo foo2bar foo2quux foo2foo bar2foo quux2foo
Number of sub-strings matching the pattern: 15 
00: foo
01: foofoo
02: foobar
03: fooquux
04: barfoo
05: quuxfoo
06: foofoo
07: barfoo
08: quuxfoo
09: foo2foo
10: foo2bar
11: foo2quux
12: foo2foo
13: bar2foo
14: quux2foo

Но если мне нужно (и я буду) добавлять больше шаблонов для поиска в более сложной строке, это быстро становится грязным, и я путаюсь с последовательностью альтернативных шаблонов ('|').

Есть ли кто-нибудь, кто мог бы помочь мне написать более короткое / более чистое регулярное выражение шаблона, чтобы разделить слово / подслово 'foo' (или любое другое) так, чтобы его можно было записать одним шаблоном?

Заранее спасибо.

GM

Strawberry 5.022 на W7 / 64, но я думаю, что это довольно универсально для любого Perl выше 5.016 или даже 5.008;


Я нашел решение Дауг Штеффен тоже) подходящим для меня.Не самый читабельный, grep больше соответствует моему уровню Perl, но я думаю, что, основываясь исключительно на регулярном выражении, он способен обрабатывать добавление слов в будущем с помощью обработки пределов слов .

$s=~/(?:(?<=\h)|^)(\w*foo\w*)(?=\h|$)/g


(?:(?<=\h)|^)  Assert either after a \h (horizontal space) or at start of line ^
(\w*foo\w*)    Capture a 'word' with 'foo' and only \w characters (or, [a-zA-Z0-9_] characters)
(?=\h|$)       Assert before either a \h horizontal space or end of line $

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

(?:         # You start a non capturing group.
(?<=        # You start a lookbehind (so non capturing BY NATURE, am I right ?, because
            # if not, as it is being enclosed in round-brackets '()' it restarts to be
            # capturing even inside a non capturing group, isn't it?)
 \h         # In the lookbehind you look for an horizontal space (could \s have been used
            # there?)
 ^          # in the non capturing group but outside of the lookbehind you look for the
            # start of string anchor. Must not be present in the lookbehind group because
            # it requires a same length pattern size and ^ has length==0 while \h is
            # non zero.
\w*foo\w*   # You look for foo within an alphanum word. No pb to have '*' rather than '+'
            # because your left (and right, that we'll see it down) bound has been well
            # restricted.
(?=         # You start a lookforward pattern (non capturing by nature here again, right?),
            # to look for:
\h or $     # horiz space or end of string anchor. However the lookaround size is
            # different here as $ is still 0 length (as ^ anchor) and \h still non
            # zero. "AND YET IT MOVES" (I tested your regexp and it worked) because
            # only the lookbehind has the 'same-size' pattern restriction, right?

СпасибоЗа вашу помощь, все вы, после этого последнего пункта я не буду больше беспокоить вас своими маленькими проблемами и считаю, что мой вопрос полностью отвечен.Г.

Ответы [ 3 ]

0 голосов
/ 11 октября 2018

Это зависит: если вы хотите получить foobar из (foobar), это легко.Вы просто сопоставляете foo с необязательными символами слова до и после, а затем с обеих сторон границу слова \b (которая может начинаться с ввода или какой-либо несловесный символ):

my @m = ($s=~/(\b\w*foo\w*\b)/g);
print( sprintf("%02d: ",$_),
    ($s=~/(\b\w*foo\w*\b)/g)[$_],
    qq(\n) )
for (0..@m-1);

Вывод:

00: foo
01: foofoo
02: foobar
03: fooquux
04: barfoo
05: quuxfoo
06: foo
07: foo
08: foo
09: foofoo
10: barfoo
11: quuxfoo
12: foo2foo
13: foo2bar
14: foo2quux
15: foo2foo
16: bar2foo
17: quux2foo

Если нет, то это немного сложнее.Здесь я сопоставляю начало ввода или пробел, затем foo, окруженный необязательными символами слова, и тогда нам нужно утверждение (нулевой длины), которое требует пробела или конца ввода:

my @m = ($s=~/(?:^|\s)(\w*foo\w*)(?=\s|$)/g);
print( sprintf("%02d: ",$_),
    ($s=~/(?:^|\s)(\w*foo\w*)(?=\s|$)/g)[$_],
    qq(\n) )
for (0..@m-1);

Вывод:

00: foo
01: foofoo
02: foobar
03: fooquux
04: barfoo
05: quuxfoo
06: foofoo
07: barfoo
08: quuxfoo
09: foo2foo
10: foo2bar
11: foo2quux
12: foo2foo
13: bar2foo
14: quux2foo
0 голосов
/ 11 октября 2018

Вы можете разбить вашу строку и отфильтровать массив:

use strict;
use warnings;

my $s=
'foo bar quux foofoo foobar fooquux barfoo barbar barquux quuxfoo quuxbar quuxquux ' .
'[foo] (foo) {foo} foofoo barfoo quuxfoo foo2foo foo2bar foo2quux foo2foo bar2foo quux2foo';

my @res = grep {/foo/ && !/\W/}  split /\s/, $s;

print join(" ", @res);
0 голосов
/ 11 октября 2018

Возможно, сначала отфильтруйте нежелательные слова, а затем используйте grep против отфильтрованных слов:

use strict;
use warnings;

my $s=
'foo bar quux foofoo foobar fooquux barfoo barbar barquux quuxfoo quuxbar quuxquux ' .
'[foo] (foo) {foo} foofoo barfoo quuxfoo foo2foo foo2bar foo2quux foo2foo bar2foo quux2foo';

my @words = ( $s=~/(?:(?<=\h)|^)(\w+)(?=\h|$)/g );

my @foos = grep(/foo/, @words);

while (my ($i, $v) = each @foos) {
    printf "%02d: %s\n", $i,$v;
}

Отпечатки:

00: foo
01: foofoo
02: foobar
03: fooquux
04: barfoo
05: quuxfoo
06: foofoo
07: barfoo
08: quuxfoo
09: foo2foo
10: foo2bar
11: foo2quux
12: foo2foo
13: bar2foo
14: quux2foo

В качестве альтернативы, вы можете комбинировать фильтрацию по списку словразделить на горизонтальные пробелы и проверить полученное слово буквенно-цифровым:

@foos=grep {/foo/ && /^\w+$/} split /\h/, $s;  # same result

Или

@foos=grep {/^\w*foo\w*$/} split /\h/, $s; 

Или в одном регулярном выражении :

@foos=($s=~/(?:(?<=\h)|^)(\w*foo\w*)(?=\h|$)/g);

В соответствии с просьбой в комментариях:

$s=~/(?:(?<=\h)|^)(\w*foo\w*)(?=\h|$)/g


(?:(?<=\h)|^)  Assert either after a \h (horizontal space) or at start of line ^
(\w*foo\w*)    Capture a 'word' with 'foo' and only \w characters (or, [a-zA-Z0-9_] characters)
(?=\h|$)       Assert before either a \h horizontal space or end of line $

Единственная сложная часть - (?:(?<=\h)|^).В Perl запрещено иметь просмотр без фиксированной ширины, такой как (?<=\h|^), поскольку ^ - нулевая ширина, а \h - нет.(Интересно, что регулярное выражение (?<=\h|^) допустимо в библиотеке PCRE.) Таким образом, (?:(?<=\h)|^) разбивает два утверждения на одну группу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...