Яка різниця між "групами" та "захопленнями" у регулярних виразах .NET?


161

Мені трохи нечітко в чому різниця між "групою" та "захопленням", коли мова йде про регулярну мову вираження .NET. Розглянемо наступний код C #:

MatchCollection matches = Regex.Matches("{Q}", @"^\{([A-Z])\}$");

Я очікую, що це призведе до одного запису для літери "Q", але якщо я надрукую властивості повернутого MatchCollection, я побачу:

matches.Count: 1
matches[0].Value: {Q}
        matches[0].Captures.Count: 1
                matches[0].Captures[0].Value: {Q}
        matches[0].Groups.Count: 2
                matches[0].Groups[0].Value: {Q}
                matches[0].Groups[0].Captures.Count: 1
                        matches[0].Groups[0].Captures[0].Value: {Q}
                matches[0].Groups[1].Value: Q
                matches[0].Groups[1].Captures.Count: 1
                        matches[0].Groups[1].Captures[0].Value: Q

Що саме тут відбувається? Я розумію, що також є захоплення на весь матч, але як входять групи? І чому не matches[0].Capturesвключає захоплення для літери "Q"?

Відповіді:


126

Ви не будете першим, хто з цим розмивається. Ось що про це має сказати відомий Джеффрі Фрідл (стор. 437+):

Залежно від вашої точки зору, це або додає цікавого нового виміру результатам матчу, або додає розгубленості та роздутості.

І далі:

Основна відмінність об’єкта групи від об'єкта Capture полягає в тому, що кожен об'єкт групи містить колекцію Captures, що представляє всі посередницькі збіги групою під час матчу, а також кінцевий текст, який відповідає групі.

І кілька сторінок пізніше, ось такий його висновок:

Після проходження документації .NET і власне розуміння того, що ці об’єкти додають, у мене виникають неоднозначні почуття щодо них. З одного боку, це цікаве нововведення [..], з іншого боку, це, здається, додає навантаження на ефективність [..] функціоналу, який не використовується в більшості випадків

Іншими словами: вони дуже схожі, але час від часу, і як це трапляється, ви знайдете для них користь. Перш ніж виростити ще одну сиву бороду, ви, можливо, навіть полюбите Захоплення ...


Оскільки ні сказане, ні те, що сказано в іншій публікації, справді, здається, не відповідають на ваше запитання, врахуйте наступне. Подумайте про Captures як про своєрідний трекер історії. Коли регулярний вираз поєднує його, він проходить через рядок зліва направо (ігноруючи зворотний трек на хвилину), і коли він зіткнеться з відповідними дужками, що захоплюють, він збереже це в $x(x - будь-яка цифра), скажімо $1.

Нормальні двигуни регулярного виразів, коли повторювальні дужки повинні бути повторені, викинуть струм $1і замінять його новим значенням. Не .NET, який буде зберігати цю історію та розміщувати її Captures[0].

Якщо ми змінимо ваш регулярний вигляд на такий:

MatchCollection matches = Regex.Matches("{Q}{R}{S}", @"(\{[A-Z]\})+");

ви помітите, що у першої Groupбуде одна Captures(перша група завжди буде цілим матчем, тобто дорівнює $0), а друга група буде тримати {S}, тобто лише остання відповідна група. Однак, і ось ось улов, якщо ви хочете знайти два інших улову, вони є в них Captures, який містить усі посередницькі захоплення для {Q} {R}і {S}.

Якщо ви коли-небудь замислювалися про те, як ви могли дістатись до функції багаторазового захоплення, яка показує лише останню відповідність окремим захопленням, які явно є в рядку, ви повинні використовувати Captures.

Заключне слово щодо вашого остаточного запитання: загальна відповідність завжди має один загальний захоплення, не змішуйте це з окремими групами. Захоплення цікаві лише всередині груп .


1
a functionality that won't be used in the majority of casesЯ думаю, що він пропустив човен. За короткий термін (?:.*?(collection info)){4,20}підвищується ефективність більш ніж на кілька сотень відсотків.

1
@sln, не впевнений, на що ти звертаєшся і хто «він» (friedl?). Приклад, який ви наводите, здається не пов'язаним з цією дискусією або з використаними виразами. Крім того, не жадібні квантори є дуже рідко ефективнішими, ніж жадібні квантори, і вимагають знання набору даних та ретельного тестування перф.
Авель

@Abel - я приземлився тут із запитання, позначеного як дублікат цього. Бачу, цитував Фрідля. Цей пост є старим і його потрібно оновити, щоб він був сучасним. Тільки за допомогою Dot Net це можна зробити, саме це відрізняє від більшості інших. Розбивка: кількісно визначений загальний приклад групи, що не охоплює (?:..)+. Ліниво порівнюйте що завгодно .*?до виразу (групи) захоплення. Продовжуйте далі. У межах одного матчу груповий збір осаджує масив саме того, що потрібно. Немає необхідності в пошуку наступного, немає повторного входу, що робить це 10 - 20 і більше разів швидшим.

1
@sln, це питання стосується чогось іншого, а саме про .net-особливості, яку не можна знайти в інших движках регулярних виразів (групи проти захоплення, див. заголовок). Я не бачу тут нічого застарілого, .net все ще працює так само, адже ця частина давно не змінилася в .net. Виступ не є частиною питання. Так, групування без захоплення відбувається швидше, але знову ж таки, тема тут навпаки. Чому жадібний швидше ледачий, пояснюється в багатьох текстах в Інтернеті та у книзі Фрідла, але тут все ж. Може, інше питання (яке?) Не було справжнім дублікатом?
Авель

2
@Abel - Я знаю, що я все продовжую це говорити, але ти не чуєш цього. Я сприймаю це твердження Фрідля a functionality that won't be used in the majority of cases. Насправді це найбільш затребувана функціональність у регекс-землі. Ледачий / жадібний? Що це стосується моїх коментарів? Це дозволяє мати змінну кількість буферів захоплення. Він може підмітати всю струну за один матч. Якщо .*?(dog)знайде першого, dogто (?:.*?(dog))+знайде всіх dog у всій рядку за один збіг. Підвищення продуктивності помітно.

20

Група - це те, що ми пов’язуємо з групами в регулярних виразах

"(a[zx](b?))"

Applied to "axb" returns an array of 3 groups:

group 0: axb, the entire match.
group 1: axb, the first group matched.
group 2: b, the second group matched.

за винятком того, що це лише «захоплені» групи. Групи, які не захоплюють (використовуючи синтаксис '(?:') Тут не представлені.

"(a[zx](?:b?))"

Applied to "axb" returns an array of 2 groups:

group 0: axb, the entire match.
group 1: axb, the first group matched.

Захоплення - це також те, що ми асоціювали з «захопленими групами». Але коли група застосовується за допомогою кількісного показника кілька разів, лише останній збіг зберігається як збіг групи. Масив захоплень зберігає всі ці збіги.

"(a[zx]\s+)+"

Applied to "ax az ax" returns an array of 2 captures of the second group.

group 1, capture 0 "ax "
group 1, capture 1 "az "

Що стосується вашого останнього запитання - я б подумав, перш ніж розбиратися в цьому, що Captures буде масивом зйомок, упорядкованих групою, до якої вони належать. Скоріше це лише псевдонім для груп [0] .Записи. Досить марно ..


Чітке пояснення (у)
Ghasan

19

Це можна пояснити простим прикладом (і малюнками).

Збіг 3:10pmз регулярним виразом ((\d)+):((\d)+)(am|pm)та використання Mono interactive csharp:

csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)").
      > Groups.Cast<Group>().
      > Zip(Enumerable.Range(0, int.MaxValue), (g, n) => "[" + n + "] " + g);
{ "[0] 3:10pm", "[1] 3", "[2] 3", "[3] 10", "[4] 0", "[5] pm" }

То де ж 1? введіть тут опис зображення

Оскільки є кілька цифр, які відповідають четвертій групі, ми «отримуємо» останній збіг лише в тому випадку, якщо ми посилаємось на групу (з неявною ToString(), тобто). Для того, щоб розкрити проміжні відповідники, нам потрібно пройти глибше і віднести Capturesвластивість до групи, про яку йдеться:

csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)").
      > Groups.Cast<Group>().
      > Skip(4).First().Captures.Cast<Capture>().
      > Zip(Enumerable.Range(0, int.MaxValue), (c, n) => "["+n+"] " + c);
{ "[0] 1", "[1] 0" }

введіть тут опис зображення

Надано цією статтею .


3
Приємна стаття. Малюнок вартує тисячі слів.
AlexWei

Ви - зірка.
mikemay

14

З документації MSDN :

Реальна корисність властивості Captures відбувається, коли до групи захоплення застосовується кількісний показник, щоб група захоплювала кілька підрядів в одному регулярному виразі. У цьому випадку об'єкт Group містить інформацію про останню захоплену підрядку, тоді як властивість Captures містить інформацію про всі підряди, захоплені групою. У наступному прикладі регулярний вираз \ b (\ w + \ s *) +. відповідає цілому реченню, яке закінчується періодом. Група (\ w + \ s *) + фіксує окремі слова колекції. Оскільки колекція Групи містить інформацію лише про останню захоплену підрядку, вона фіксує останнє слово у реченні "речення". Однак кожне слово, захоплене групою, доступне у колекції, поверненій властивістю Captures.


4

Уявіть, що у вас є наступне введення тексту dogcatcatcatта такий зразокdog(cat(catcat))

У цьому випадку у вас є 3 групи, перша ( основна група ) відповідає збігу.

Матч == dogcatcatcatі Group0 ==dogcatcatcat

Група1 == catcatcat

Група2 == catcat

То про що це все?

Розглянемо невеликий приклад, написаний у C # (.NET) за допомогою Regexкласу.

int matchIndex = 0;
int groupIndex = 0;
int captureIndex = 0;

foreach (Match match in Regex.Matches(
        "dogcatabcdefghidogcatkjlmnopqr", // input
        @"(dog(cat(...)(...)(...)))") // pattern
)
{
    Console.Out.WriteLine($"match{matchIndex++} = {match}");

    foreach (Group @group in match.Groups)
    {
        Console.Out.WriteLine($"\tgroup{groupIndex++} = {@group}");

        foreach (Capture capture in @group.Captures)
        {
            Console.Out.WriteLine($"\t\tcapture{captureIndex++} = {capture}");
        }

        captureIndex = 0;
    }

    groupIndex = 0;
    Console.Out.WriteLine();
        }

Вихід :

match0 = dogcatabcdefghi
    group0 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group1 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group2 = catabcdefghi
        capture0 = catabcdefghi
    group3 = abc
        capture0 = abc
    group4 = def
        capture0 = def
    group5 = ghi
        capture0 = ghi

match1 = dogcatkjlmnopqr
    group0 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group1 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group2 = catkjlmnopqr
        capture0 = catkjlmnopqr
    group3 = kjl
        capture0 = kjl
    group4 = mno
        capture0 = mno
    group5 = pqr
        capture0 = pqr

Проаналізуємо лише перший матч ( match0).

Як ви можете бачити , що є три невеликі групи : group3, group4іgroup5

    group3 = kjl
        capture0 = kjl
    group4 = mno
        capture0 = mno
    group5 = pqr
        capture0 = pqr

Ці групи (3-5) були створені через « подмаскі » (...)(...)(...)з основного шаблона (dog(cat(...)(...)(...)))

Значення group3відповідає його схопленню ( capture0). (Як у випадку group4і group5). Це тому, що немає подібних групових повторів(...){3} .


Гаразд, розглянемо ще один приклад, коли є групове повторення .

Якщо ми змінимо шаблон регулярного виразу , щоб бути узгоджені (для коду , показаних вище) від (dog(cat(...)(...)(...)))до (dog(cat(...){3})), ви помітите , що існує наступна група повторення : (...){3}.

Тепер Вихід змінився:

match0 = dogcatabcdefghi
    group0 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group1 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group2 = catabcdefghi
        capture0 = catabcdefghi
    group3 = ghi
        capture0 = abc
        capture1 = def
        capture2 = ghi

match1 = dogcatkjlmnopqr
    group0 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group1 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group2 = catkjlmnopqr
        capture0 = catkjlmnopqr
    group3 = pqr
        capture0 = kjl
        capture1 = mno
        capture2 = pqr

Знову ж таки, давайте проаналізуємо лише перший матч ( match0).

Більше немає другорядних груп, group4 і group5через (...){3} повторення ( {n} де n> = 2 ) вони були об'єднані в одну єдину групу group3.

У цьому випадку group3значення відповідає його capture2( останнє захоплення , іншими словами).

Таким чином , якщо вам потрібні всі 3 внутрішніх захоплення ( capture0, capture1, capture2) , вам доведеться цикл через групу Capturesзбір.

Висновок: зверніть увагу на те, як ви проектуєте групи шаблонів. Ви повинні думати заздалегідь , яка поведінка викликає специфікацію групи, як (...)(...), (...){2}або і (.{3}){2}т.д.


Сподіваємось, це допоможе пролити трохи світла на відмінності між захопленнями , групами та матчами .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.