2015-08-08 5 views
3

У меня есть текстовый файл, который в настоящее время обрабатывается выражением regex, и он работает хорошо. Формат файла хорошо определен, 2 числа, разделенные пробелами, а затем необязательный комментарий.Regex Соответствие необязательным номерам

Теперь у нас есть необходимость добавить дополнительное (но необязательное) третье число в этот файл, в результате чего формат, 2 или 3 номера разделяются пробелом с необязательным комментарием.

У меня есть объект regex, который, по крайней мере, соответствует всем необходимым форматам линии, но мне не повезло с фактическим захватом третьего (дополнительного) номера, даже если он присутствует.

Код:

#include <iostream> 
#include <regex> 
#include <vector> 
#include <string> 
#include <cassert> 
using namespace std; 

bool regex_check(const std::string& in) 
{ 
    std::regex check{ 
     "[[:space:]]*?"     // eat leading spaces 
     "([[:digit:]]+)"     // capture 1st number 
     "[[:space:]]*?"     // each second set of spaces 
     "([[:digit:]]+)"     // capture 2nd number 
     "[[:space:]]*?"     // eat more spaces 
     "([[:digit:]]+|[[:space:]]*?)"  // optionally, capture 3rd number 
     "!*?"        // Anything after '!' is a comment 
     ".*?"        // eat rest of line 
    }; 

    std::smatch match; 

    bool result = std::regex_match(in, match, check); 

    for(auto m : match) 
    { 
     std::cout << " [" << m << "]\n"; 
    } 

    return result; 
} 

int main() 
{ 
    std::vector<std::string> to_check{ 
     " 12 3", 
     " 1 2 ", 
     " 12 3 !comment", 
     " 1 2  !comment ", 
     "\t1\t1", 
     "\t 1\t 1\t !comment \t", 
     " 16653 2  1", 
     " 16654 2  1 ", 
     " 16654 2  1 ! comment", 
     "\t16654\t\t2\t 1\t ! comment\t\t", 
    }; 

    for(auto s : to_check) 
    { 
     assert(regex_check(s)); 
    } 

    return 0; 
} 

Это дает следующий результат:

[ 12 3] 
    [12] 
    [3] 
    [] 
    [ 1 2 ] 
    [1] 
    [2] 
    [] 
    [ 12 3 !comment] 
    [12] 
    [3] 
    [] 
    [ 1 2  !comment ] 
    [1] 
    [2] 
    [] 
    [ 1 1] 
    [1] 
    [1] 
    [] 
    [ 1 1 !comment  ] 
    [1] 
    [1] 
    [] 
    [ 16653 2  1] 
    [16653] 
    [2] 
    [] 
    [ 16654 2  1 ] 
    [16654] 
    [2] 
    [] 
    [ 16654 2  1 ! comment] 
    [16654] 
    [2] 
    [] 
    [ 16654  2  1  ! comment  ] 
    [16654] 
    [2] 
    [] 

Как вы можете видеть, это соответствие всех ожидаемых входных форматов, но никогда не в состоянии фактически захватить 3-й номер , даже если он присутствует.

В настоящее время я тестирую это с помощью GCC 5.1.1, но этот фактический целевой компилятор будет GCC 4.8.2, используя boost::regex вместо std::regex.

ответ

3

Давайте сделаем пошаговую обработку в следующем примере.

16653 2  1 
^ 

^ является в настоящее время -ее смещение. На данный момент, мы здесь в схеме:..

\s*?(\d+)\s*?(\d+)\s*?(\d+|\s*?)!*?.*? 
^    

(я упростил [[:space:]] к \s и [[:digit:]] в \d для brievty


\s*? матчи, а затем (\d+) матчи Мы в конечном итоге в следующем состоянии:

16653 2  1 
    ^
\s*?(\d+)\s*?(\d+)\s*?(\d+|\s*?)!*?.*? 
     ^

То же самое: \s*? матчи, а затем (\d+) матчи. Государство:

16653 2  1 
     ^
\s*?(\d+)\s*?(\d+)\s*?(\d+|\s*?)!*?.*? 
       ^

Теперь все становится сложнее.

У вас есть \s*? здесь, ленивый квантор. Двигатель пытается не соответствовать чему угодно, и видит, будет ли остальная часть шаблона соответствовать. Поэтому он пытается чередовать.

Первым вариантом является \d+, но он не работает, поскольку у вас нет цифры в этом положении.

Второй вариант: \s*?, и после этого нет других альтернатив. Это лениво, поэтому давайте сначала попытаемся совместить пустую строку.

Следующий маркер !*?, но также соответствует пустой строке, и затем следует .*?, который будет соответствовать все до конца строки (он делает это потому, что вы используете regex_match - он сопоставил бы пустую строку с regex_search).

На этом этапе вы успешно достигли конца шаблона, и вы получили совпадение, не будучи вынуждены сопоставлять \d+ с строкой.

Дело в том, вся эта часть шаблона заканчивается время опциональной:

\s*?(\d+)\s*?(\d+)\s*?(\d+|\s*?)!*?.*? 
        \__________________/ 

Итак, что вы можете сделать? Вы можете переписать шаблон следующим образом:

\s*?(\d+)\s+(\d+)(?:\s+(\d+))?\s*(?:!.*)? 

Demo (с дополнительными якорями, чтобы имитировать поведение regex_match)

Таким образом, вы заставляете движок регулярных выражений, чтобы рассмотреть \d и не уйти с ленивой-согласование на пустой строке. Нет необходимости в ленивых квантификаторах, поскольку \s и \d не пересекаются.

!*?.*? также был субоптимальным, поскольку !*? уже охвачен следующими .*?. Я переписал его, так как (?:!.*)? - требует a ! в начале комментария, если его нет.

+2

Я бы лучше спал, если бы это было что-то вроде '\ s * (\ d +) \ s + (\ d +) (?: \ S + (\ d +))? \ S * (?:!. *)?' , Нежелательные квантификаторы заставляют меня нервничать. – melpomene

+1

Я пробовал ваше регулярное выражение, и оно соответствовало строке «12». – melpomene

+1

@melpomene да, спасибо, я ответил слишком быстро и не думал обо всех причудах оригинальной картины. –

Смежные вопросы