Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Общие вопросы по .NET и C# > Alternation Constructs в .NET Regexp


Автор: FiMa1 28.8.2008, 14:13
Добрейшего времени суток, друзья.

Столкнулся со следующей проблемой:
Имеем следующую теорию http://msdn.microsoft.com/en-us/library/36xybswe(VS.71).aspx, пытаюсь использовать '(?(expression)yes|no)' регулярное выражение. Входной текст: "abcdefg", пишу (?(abc)defg|"") - получаю 'No Matches' (конструктор регулярных выражений 'RAD Software Regular Expression Designer 1.4.7'). Если кто-нибудь сталкивался с подобным или знает в чем дело, помогите, пожалуйста.

Автор: Bishop 28.8.2008, 15:37
FiMa1, 

Вы хотите "abc" использовать как шаблон, а оно воспринимается как name. Укажите явно: (?(?<=abc)defg|"").

http://msdn.microsoft.com/en-us/library/bs2twtah.aspx.
http://www.pcre.ru/docs/php/text/stdsyntax/ — разделы Утверждения и Условные подмаски.

Автор: FiMa1 28.8.2008, 16:24
Цитата(Bishop @ 28.8.2008,  15:37)
FiMa1, 

Вы хотите "abc" использовать как шаблон, а оно воспринимается как name. Укажите явно: (?(?<=abc)defg|"").

http://msdn.microsoft.com/en-us/library/bs2twtah.aspx.
http://www.pcre.ru/docs/php/text/stdsyntax/ — разделы Утверждения и Условные подмаски.

Bishop, большое спасибо!

Я пробежался глазами по приведенным ссылкам, пока не уяснил для себя, что можно считать "именем"? Никогда до этого сталкивался с этим понятием.

(?(name)yes|no) 
Matches the "yes" part if the named capture string has a match; otherwise, matches the "no" part. The "no" part can be omitted. If the given name does not correspond to the name or number of a capturing group used in this expression, the alternation construct is interpreted as an expression test (described in the preceding row of this table).

Не подскажите вкратце, если есть минутка. Был бы еще больше признателен smile .

Автор: Bishop 28.8.2008, 19:15
FiMa1, группы (подмаски), т.е. выражения в скобках можно именовать, тогда к ним можно будет обращаться не по номерам, а по этим именам. 
Код
Match m = Regex.Match("[email protected]", @"(?<addr>.*?)@(?<host>.*?\..{2,})");
string msg = string.Format("{0}\n{1}\n{2}\n{3}",
    m.Groups[1].Value, // address
    m.Groups["addr"].Value, // address
    m.Groups[2].Value, // mail.ru
    m.Groups["host"].Value // mail.ru
);

В случае альтернатив:
Код
Regex.Match("abcdefgh", @"(?<name>abc)(?(name)de|.*)"); // abcde
Regex.Match("abcdefgh", @"(?<name>abc)(?(name1)de|.*)"); //abcdefgh

И это наиболее понятный способ их использования.
Я похоже ошибся в прошлом посте:
Код
Regex.Match("abcdefgh", @"(?(abc)abcde|.*)"); // abcde
Regex.Match("abcdefgh", @"(?(bc)abcde|.*)"); // abcdefgh

Т.е. все работает, но анализ идет четко с начала строки, как будто там не abc, а ^abc.
Код
Regex.Match("abcdefgh", @"(?(abc)f|.*)"); // bcdefgh

А этот результат объяснить не могу  smile .

Автор: source777 28.8.2008, 21:51
Цитата(Bishop @  28.8.2008,  19:15 Найти цитируемый пост)
Я похоже ошибся в прошлом посте:
...
Т.е. все работает, но анализ идет четко с начала строки, как будто там не abc, а ^abc.

В этом посте ты тоже к сожалению ошибся, это заметно, например, на таком примере:
Код

Regex.Match("abcnoyes", @"(?(abc)yes|no)"); // no
Regex.Match("abcnoyes", @"(?(.*)yes|no)"); // yes

Т.е. во всех 3 нижних строках кода из твоего поста фактически срабатывает условие "no", но срабатывает как-то глючно, в чём ты впрочем не виноват  smile 

Автор: Bishop 29.8.2008, 01:51
source777, 
В первой из трёх нижних строк выполняется yes. А в остальных всё тоже правильно, то есть не "глючно" smile
Код
(?(expression)yes|no)

Я почему-то подумал (и похоже, ты тоже), что эта конструкция работает по логике if-then-else: если expression истинно, то ищется шаблон yes, иначе шаблон no. Но это не так. 
Выражение "yes" — это вся часть (expression)yes. А (expression), если не указано явно, это (?=expression). Фактически, это сокращенная запись, которую можно развернуть в:
Код
(?(expr)yes|no) === (?:(?=expr)yes|no)

И тогда все ясно с примерами:
Место (abc) === (?=abc) в строке находится "(_здесь_)abcdefgh"
Код
Regex.Match("abcdefgh", @"(?(abc)abcde|.*)"); // abcde
Regex.Match("abcnoyes", @"(?(abc)yes|no)"); // no

Потому что:
Код
Regex.IsMatch("abcdefgh", @"(?=abc)abcde"); // true
Regex.IsMatch("abcnoyes", @"(?=abc)yes"); // false


Автор: FiMa1 29.8.2008, 08:59
Bishop, source777, большое спасибо за участие! Теперь понятно с именами и почему я с ними не сталкивался, я не использовал регулярные выражения непосредственно в коде на C#.

Автор: source777 29.8.2008, 10:54
Цитата(Bishop @  29.8.2008,  01:51 Найти цитируемый пост)
Я почему-то подумал (и похоже, ты тоже), что эта конструкция работает по логике if-then-else: если expression истинно, то ищется шаблон yes, иначе шаблон no. 
да, это ты точно подметил. Хотя подобное заблуждение казалось вполне логичным  smile 

Однако, всё равно остались невыясненные моменты, например,
Код

Regex.Match("abcyesno", @"(?(yes)yes|no)"); // yes
Regex.Match("abcyesno", @"(?(yes)yes|.*)"); // abcyesno
Regex.IsMatch("abcyesno", @"(?=yes)yes"); // true

Казалось бы, поскольку IsMatch == true, то в Match.Value всегда должно быть "yes", в независимости от "no" - части, однако это не так... smile 
Причём если использовать в качестве входной строки "yesno", то так и будет, однако такая жёсткая привязка в началу строки не всегда подходит, в качестве воркэраунда насколько я выяснил сработает: 
Код

Regex.Match("abcyesno", @"(?(.*yes)yes|.*)"); // т.е. просто пропускаем начало строки вплоть до нужного нам шаблона...
 

Автор: Bishop 29.8.2008, 19:45
source777, 
Цитата(source777 @  29.8.2008,  10:54 Найти цитируемый пост)
Казалось бы, поскольку IsMatch == true, то в Match.Value всегда должно быть "yes", в независимости от "no" - части, однако это не так...  

Не, не так. Оно не ищет условие, идет последовательный анализ строки и, если условие ложно, а часть NO в этой позиции находит совпадение, разбор прекращается. Смотри:
Код
Regex.Match("abcyesno", @"(?(yes)yes|.*)"); // abcyesno

Причем (yes) это сокращенная запись утверждения (?=yes).
Разбор начинается с начала строки: на символе "a" (?=yes) ложно — переход к части NO; .* в этой позиции совпадает со всей строкой — это результат.
Код
Regex.Match("abcyesno", @"(?(yes)yes|no)"); // yes

Здесь разбор идет так: "a" — (?=yes) ложно, часть NO в текущей позиции не совпадает; "b" — тоже; "c" — тоже; "y" — (?=yes) истинно, (?=yes)yes совпадает — это результат.
И правильный вариант:
Код
Regex.Match("abcyesno", @"(?(.*yes)yes|.*)");

Этапы разбора: "a" — (?=.*yes) истинно, но (?=.*yes)yes не совпадает, .* пропускается; "b" — тоже; "c" — тоже; "y" — (?=.*yes) истинно, (?=.*yes)yes совпадает — это результат.
Тогда всё ясно с непонятным результатом выражения из моего позапрошлого поста:
Код
Regex.Match("abcdefgh", @"(?(abc)f|.*)"); // bcdefgh

Разбор: "a" — (?=abc) истинно, но (?=abc)f не совпадает, .* пропускается; "b" — (?=abc) ложно, .* совпадает с "bcdefgh" — это результат. Не вся строка, т.к. "a" уже пропущена, а разбор без особой нужды в обратную сторону не идёт.

Может я ошибаюсь на счёт внутренней работы regex engine, но концептуально оно вот так.
Т.е. не ищется совпадение expression по всей строке (как мы думали), оно вычисляется на общих основаниях по мере разбора строки. От простой альтернативы отличается тем, что пока условие истинно совпадения в блоке NO не ищутся. Вот:
Код
Regex.Match("abcyes abcno", @"(?(abc)abcno|abcyes)");  // abcno
Regex.Match("abcyes abcno", @"(?:abcno|abcyes)"); // abcyes


В описании этапов под буквами подразумеваются Zero-width позиции перед ними.
Пытался объяснить, надеюсь не запутал ещё больше smile 

Автор: source777 30.8.2008, 16:19
Bishop, спасибо, более менее прояснилась ситуация  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)