Як групи, що не захоплюють, тобто (?:)
використовуються в регулярних виразах і для чого вони хороші?
Як групи, що не захоплюють, тобто (?:)
використовуються в регулярних виразах і для чого вони хороші?
Відповіді:
Дозвольте спробувати пояснити це на прикладі.
Розглянемо наступний текст:
http://stackoverflow.com/
/programming/tagged/regex
Тепер, якщо я застосую реджекс нижче над ним ...
(https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?
... Я отримав би такий результат:
Match "http://stackoverflow.com/"
Group 1: "http"
Group 2: "stackoverflow.com"
Group 3: "/"
Match "/programming/tagged/regex"
Group 1: "https"
Group 2: "stackoverflow.com"
Group 3: "/questions/tagged/regex"
Але мене не хвилює протокол - я просто хочу хост і шлях URL-адреси. Отже, я змінюю регулярний вираз, щоб включати групу, яка не захоплює (?:)
.
(?:https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?
Тепер мій результат виглядає приблизно так:
Match "http://stackoverflow.com/"
Group 1: "stackoverflow.com"
Group 2: "/"
Match "/programming/tagged/regex"
Group 1: "stackoverflow.com"
Group 2: "/questions/tagged/regex"
Подивитися? Перша група не була захоплена. Аналізатор використовує його для узгодження тексту, але ігнорує його пізніше, у кінцевому результаті.
Як вимагається, дозвольте мені також спробувати пояснити групи.
Ну а групи служать багатьом цілям. Вони можуть допомогти вам отримати точну інформацію з більшої відповідності (яку також можна назвати), вони дозволять вам переймати попередню відповідну групу і можуть бути використані для заміни. Спробуємо кілька прикладів, чи не так?
Уявіть, що у вас є якийсь XML або HTML (пам’ятайте, що регулярний вираз не може бути найкращим інструментом для роботи , але це приємно як приклад). Ви хочете розібрати теги, щоб ви могли зробити щось подібне (я додав пробіли, щоб полегшити розуміння):
\<(?<TAG>.+?)\> [^<]*? \</\k<TAG>\>
or
\<(.+?)\> [^<]*? \</\1\>
Перший регулярний вираз має іменовану групу (TAG), а другий використовує загальну групу. Обидва регулярні вирази роблять те саме: вони використовують значення з першої групи (назва тегу), щоб відповідати завершальному тегу. Різниця полягає в тому, що перший використовує ім'я, щоб відповідати значенню, а другий використовує груповий індекс (який починається з 1).
Давайте спробуємо деякі заміни зараз. Розглянемо наступний текст:
Lorem ipsum dolor sit amet consectetuer feugiat fames malesuada pretium egestas.
Тепер давайте скористаємося цим глухим регулярним виразом:
\b(\S)(\S)(\S)(\S*)\b
Цей регулярний вираз поєднує слова з принаймні трьома символами та використовує групи для розділення перших трьох букв. Результат такий:
Match "Lorem"
Group 1: "L"
Group 2: "o"
Group 3: "r"
Group 4: "em"
Match "ipsum"
Group 1: "i"
Group 2: "p"
Group 3: "s"
Group 4: "um"
...
Match "consectetuer"
Group 1: "c"
Group 2: "o"
Group 3: "n"
Group 4: "sectetuer"
...
Отже, якщо ми застосуємо рядок підстановки:
$1_$3$2_$4
... над нею ми намагаємося використати першу групу, додати підкреслення, використовувати третю групу, потім другу групу, додати ще один підкреслення, а потім четверту групу. Отриманий рядок буде подібний до наведеного нижче.
L_ro_em i_sp_um d_lo_or s_ti_ a_em_t c_no_sectetuer f_ue_giat f_ma_es m_la_esuada p_er_tium e_eg_stas.
Ви також можете використовувати названі групи для заміни, використовуючи ${name}
.
Щоб пограти з регулярними виразами, я рекомендую http://regex101.com/ , який пропонує велику кількість деталей про те, як працює регулярний вираз; він також пропонує кілька двигунів регулярного вибору.
Ви можете використовувати групи захоплення для впорядкування та розбору виразу. Група, яка не захоплює, має першу перевагу, але не має накладних витрат на другу. Ви все ще можете сказати, що група, яка не захоплює, необов’язково, наприклад.
Скажімо, ви хочете відповідати числовому тексту, але деякі числа можна записати як 1-я, 2-а, 3-я, 4-я, ... Якщо ви хочете захопити числову частину, але не (необов'язковий) суфікс, ви можете використовувати групу, яка не захоплює .
([0-9]+)(?:st|nd|rd|th)?
Це буде відповідати числам у формі 1, 2, 3 ... або у формі 1-й, 2-й, 3-й, ..., але це лише фіксує числову частину.
?:
використовується, коли ви хочете згрупувати вираз, але ви не хочете його зберігати як збірну / захоплену частину рядка.
Прикладом може слугувати відповідність IP-адреси:
/(?:\d{1,3}\.){3}\d{1,3}/
Зауважте, що я не дбаю про збереження перших 3 октетів, але (?:...)
групування дозволяє мені скоротити регулярний вираз, не зазнаючи накладних витрат на захоплення та зберігання відповідності.
Це робить групу незахопленою, а це означає, що підрядка, відповідна цій групі, не буде включена до списку захоплень. Приклад в рубіні для ілюстрації різниці:
"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]
(?:)
не створює захоплення, не демонструвати корисний приклад (?:)
. (?:)
корисно, коли ви хочете згрупувати підвираз (скажімо, коли ви хочете застосувати квантори до неатомного підвиразу або якщо ви хочете обмежити область a |
), але ви не хочете нічого захоплювати.
ІСТОРИЧНА МОТИВАЦІЯ:
Існування груп, що не захоплюють, можна пояснити із застосуванням дужок.
Розглянемо вирази, (a|b)c
і a|bc
через пріоритет конкатенації над |
цими виразами є дві різні мови ( {ac, bc}
і {a, bc}
відповідно).
Однак дужки також використовуються як група, що відповідає (як пояснено іншими відповідями ...).
Якщо ви хочете мати дужки, але не захоплювати підвираз, ви використовуєте НЕЗАПУСТАВНІ ГРУПИ. У прикладі(?:a|b)c
Дозвольте спробувати це на прикладі:
Код Regex: (?:animal)(?:=)(\w+)(,)\1\2
Рядок пошуку:
Рядок 1 - animal=cat,dog,cat,tiger,dog
Рядок 2 - animal=cat,cat,dog,dog,tiger
Рядок 3 - animal=dog,dog,cat,cat,tiger
(?:animal)
-> Група, яка не потрапила у полон
(?:=)
-> Не захоплені 2 група
(\w+)
-> Захоплена група 1
(,)
-> Захоплена група 2
\1
-> результат захопленої групи 1, тобто в рядку 1 - кішка, в рядку 2 - кішка, в рядку 3 - собака.
\2
-> результат захопленої групи 2, тобто кома (,)
Так у цьому коді, даючи \1
і\2
ми згадуємо або повторюємо результат захопленої групи 1 та 2 відповідно згодом у коді.
Відповідно до порядку коду (?:animal)
повинні бути групи 1 і(?:=)
повинна бути група 2 і продовжується ..
але, даючи команду, ?:
ми робимо групу матчу не захопленою (які не відраховуються в збірну групу, тому число групування починається від першої захопленої групи, а не не захопленої), так що повторення результату групи матчу (?:animal)
пізніше не можна викликати в коді.
Сподіваюсь, це пояснює використання групи, яка не захоплює.
Групи, які захоплюють, ви можете використовувати пізніше в регулярному виразі для відповідності АБО ви можете використовувати їх у замінній частині регулярного виразу. Створення групи, яка не захоплює, просто звільняє її від використання з будь-якої з цих причин.
Групи, які не захоплюють, чудові, якщо ви намагаєтеся зафіксувати багато різних речей, і є деякі групи, які ви не хочете захоплювати.
Це майже причина, що вони існують. Поки ви дізнаєтесь про групи, дізнайтеся про атомні групи , вони роблять багато! Є також групи пошуку, але вони трохи складніші і не використовуються так сильно.
Приклад використання пізніше в регулярному вираженні (backreference):
<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1>
[Знаходить тег xml (без підтримки ns)]
([A-Z][A-Z0-9]*)
- це група захоплення (в даному випадку це ім'я тегів)
Пізніше в регулярному виразі - \1
це означає, що він буде відповідати лише тому ж тексту, який був у першій групі ( ([A-Z][A-Z0-9]*)
групі) (у цьому випадку він відповідає кінцевому тегу).
Я я розробник JavaScript і спробую пояснити його значення, що стосується JavaScript.
Розгляньте сценарій, коли ви хочете співставити, cat is animal
коли ви хочете відповідати кішці та тварині, і обоє повинні мати is
між ними.
// this will ignore "is" as that's is what we want
"cat is animal".match(/(cat)(?: is )(animal)/) ;
result ["cat is animal", "cat", "animal"]
// using lookahead pattern it will match only "cat" we can
// use lookahead but the problem is we can not give anything
// at the back of lookahead pattern
"cat is animal".match(/cat(?= is animal)/) ;
result ["cat"]
//so I gave another grouping parenthesis for animal
// in lookahead pattern to match animal as well
"cat is animal".match(/(cat)(?= is (animal))/) ;
result ["cat", "cat", "animal"]
// we got extra cat in above example so removing another grouping
"cat is animal".match(/cat(?= is (animal))/) ;
result ["cat", "animal"]
У складних регулярних виразах може виникнути ситуація, коли ви хочете використовувати велику кількість груп, деякі з яких є для узгодження повторень, а деякі з них для надання зворотних посилань. За замовчуванням текст, що відповідає кожній групі, завантажується в масив зворотних посилань. Там, де у нас багато груп, і лише нам потрібно мати змогу посилатися на деякі з них із масиву зворотних рефератів, ми можемо замінити цю поведінку за замовчуванням, щоб сказати регулярним виразом, що певні групи існують лише для обробки повторів і їх не потрібно захоплювати і зберігати у масиві зворотної референції.
Я не можу коментувати верхні відповіді, щоб сказати це: я хотів би додати явний пункт, який мається на увазі лише у верхніх відповідях:
Група, яка не захоплює (?...)
, не видаляє жодних символів з оригінального повного збігу, лише візуально реорганізує регулярний вираз програмісту.
Щоб отримати доступ до певної частини регулярного виразу без визначених сторонніх символів, вам завжди потрібно було б скористатися .group(<index>)
tl; dr групи, що не захоплюють, як випливає з назви, - це частини регексу, які ви не хочете включати в матч, і ?:
це спосіб визначити групу як неприхоплюючу.
Скажімо, у вас електронна адреса example@example.com
. Наступний регулярний вираз створить дві групи , частина id та частина @ example.com. (\p{Alpha}*[a-z])(@example.com)
. Для простоти ми витягуємо ціле доменне ім’я, включаючи @
символ.
Тепер скажімо, вам потрібна лише ідентифікаційна частина адреси. Те, що ви хочете зробити, - це захопити першу групу результату матчу, оточену ()
в регулярному виразі, і спосіб зробити це - використовувати синтаксис групи, що не захоплює, тобто ?:
. Таким чином, регулярний вираз (\p{Alpha}*[a-z])(?:@example.com)
поверне лише id-адресу електронної пошти.
Одна цікава річ, на яку я натрапив - це той факт, що ви можете мати групу захоплення всередині групи, яка не захоплює. Перегляньте нижче регекс для відповідності веб-адрес:
var parse_url_regex = /^(?:([A-Za-z]+):)(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
Рядок введення URL:
var url = "http://www.ora.com:80/goodparts?q#fragment";
Перша група мого регексу (?:([A-Za-z]+):)
- це група, яка не фіксує, яка відповідає схемі протоколу та :
символу двокрапки, тобто http:
коли я працював нижче коду, я бачив, що 1-й індекс повернутого масиву містить рядок, http
коли я думав про це http
і двокрапка :
про них обох не повідомлять, оскільки вони знаходяться у групі, яка не захоплює.
console.debug(parse_url_regex.exec(url));
Я подумав, що якщо перша група (?:([A-Za-z]+):)
- це група, яка не захоплює, то чому вона повертає http
рядок у вихідному масиві.
Тож якщо ви помітили, що ([A-Za-z]+)
всередині групи, що не захоплює, є вкладена група. Ця вкладена група ([A-Za-z]+)
є захоплюючою групою (що не має ?:
на початку) всередині групи, яка не захоплює (?:([A-Za-z]+):)
. Ось чому текст http
як і раніше захоплюється, але :
символ двокрапки, який знаходиться всередині групи, що не фіксує, але поза групою захоплення, не надходить у вихідний масив.
Відкрийте Google Chrome devTools, а потім вкладку Консоль: і введіть це:
"Peace".match(/(\w)(\w)(\w)/)
Запустіть його, і ви побачите:
["Pea", "P", "e", "a", index: 0, input: "Peace", groups: undefined]
Двигун JavaScript
RegExp охоплює три групи, елементи з індексами 1,2,3. Тепер використовуйте позначку, що не фіксує, щоб побачити результат.
"Peace".match(/(?:\w)(\w)(\w)/)
Результат:
["Pea", "e", "a", index: 0, input: "Peace", groups: undefined]
Це очевидно, що не є захоплюючою групою.
Думаю, я дав би вам відповідь. Не використовуйте змінні захоплення, не перевіряючи, що збіг вдався.
Змінні захоплення $1
тощо не є дійсними, якщо збіг не вдався, і вони також не очищені.
#!/usr/bin/perl
use warnings;
use strict;
$_ = "bronto saurus burger";
if (/(?:bronto)? saurus (steak|burger)/)
{
print "Fred wants a $1";
}
else
{
print "Fred dont wants a $1 $2";
}
У наведеному вище прикладі, щоб уникнути захоплення бронто $1
, (?:)
використовується.
Якщо візерунок збігається, він $1
записується як наступний згрупований візерунок.
Отже, вихід буде наступним:
Fred wants a burger
Це корисно, якщо ви не хочете, щоб збіги були збережені.
Це надзвичайно просто, ми можемо зрозуміти на прикладі простої дати, припустимо, якщо дата згадується як 1 січня 2019 року або 2 травня 2019 року або будь-яка інша дата, і ми просто хочемо перетворити її у формат dd / mm / yyyy , і нам би не потрібен місяць назва для цього питання січень чи лютий, тому для того, щоб захопити числову частину, але не (необов'язковий) суфікс, ви можете використовувати групу, яка не захоплює.
тож регулярним виразом було б,
([0-9]+)(?:January|February)?
Це так просто, як це.