如何有效地提取C++中的字符串模式?

Jam*_*mes 3 c++

我有一个模式,格式如下:

AUTHOR, "TITLE" (PAGES pp.) [CODE STATUS]
Run Code Online (Sandbox Code Playgroud)

例如,我有一个字符串

P.G. Wodehouse, "Heavy Weather" (336 pp.) [PH.409 AVAILABLE FOR LENDING]
Run Code Online (Sandbox Code Playgroud)

我想提取

AUTHOR = P.G. Wodehouse
TITLE = Heavy Weather
PAGES = 336
CODE = PH.409
STATUS = AVAILABLE FOR LENDING
Run Code Online (Sandbox Code Playgroud)

我只知道如何在Python中做到这一点,但是,有没有有效的方法在C++中做同样的事情?

Jam*_*nze 7

与Python完全相同.C++ 11有正则表达式(对于早期的C++,有Boost正则表达式.)至于读取循环:

std::string line;
while ( std::getline( file, line ) ) {
    //  ...
}
Run Code Online (Sandbox Code Playgroud)

几乎完全相同:

for line in file:
    #    ...
Run Code Online (Sandbox Code Playgroud)

唯一的区别是:

  • C++版本不会将尾部'\n'放在缓冲区中.(一般来说,C++版本在行尾处理方面可能不太灵活.)

  • 如果出现读取错误,C++版本将打破循环; Python版本将引发异常.

在你的情况下也不应该是一个问题.

编辑:

我发现虽然C++和Python中的正则表达式非常相似,但使用它们的语法并不完全相同.所以:

在C++中,通常在使用它之前声明正则表达式的实例; 像Python这样的东西re.match( r'...', line )在理论上是可行的,但不是非常惯用的(它仍然涉及在表达式中显式构造正则表达式对象).此外,match函数只返回一个布尔值; 如果你想要捕获,你需要为它们定义一个单独的对象.典型用途可能是这样的:

static std::regex const matcher( "the regular expression" );
std::smatch forCaptures;
if ( std::regex_match( line, forCaptures, matcher ) ) {
    std::string firstCapture = forCaptures[1];
    //  ...
}
Run Code Online (Sandbox Code Playgroud)

这对应于Python:

m = re.match( 'the regular expression', line )
if m:
    firstCapture = m.group(1)
    #   ...
Run Code Online (Sandbox Code Playgroud)

编辑:

另一个答案表明超载operator>>; 我衷心同意.出于好奇,我试了一下; 类似以下的东西效果很好:

struct Book
{
    std::string author;
    std::string title;
    int         pages;
    std::string code;
    std::string status;
};

std::istream&
operator>>( std::istream& source, Book& dest )
{
    std::string line;
    std::getline( source, line );
    if ( source )
    {
        static std::regex const matcher(
            R"^(([^,]*),\s*"([^"]*)"\s*\((\d+) pp.\)\s*\[(\S+)\s*([^\]]*)\])^"
            ); 
        std::smatch capture;
        if ( ! std::regex_match( line, capture, matcher ) ) {
            source.setstate( std::ios_base::failbit );
        } else {
            dest.author = capture[1];
            dest.title  = capture[2];
            dest.pages  = std::stoi( capture[3] );
            dest.code   = capture[4];
            dest.status = capture[5];
        }
    }
    return source;
}
Run Code Online (Sandbox Code Playgroud)

完成此操作后,您可以编写如下内容:

std::vector<Book> v( (std::istream_iterator<Book>( inputFile )),
                     (std::istream_iterator<Book>()) );
Run Code Online (Sandbox Code Playgroud)

并在向量的初始化中加载整个文件.

注意中的错误处理operator>>.如果一条线错了,我们设置failbit; 这是C++中的标准约定.

编辑:

由于讨论的内容很多:上述内容适用于小型,一次性程序,学校项目或一次性程序,它们将读取当前文件,以新格式输出,然后被丢弃.在生产代码中,我会坚持支持评论和空洞; 如果出现错误,则继续报告多个错误(包含行号),以及可能的延续行(因为标题可能会变得足够长而变得难以置信).operator>>如果除了需要输出行号之外没有其他原因,这样做是不切实际的,所以我将使用以下行的解析器:

int
getContinuationLines( std::istream& source, std::string& line )
{
    int results = 0;
    while ( source.peek() == '&' ) {
        std::string more;
        std::getline( source, more );   //  Cannot fail, because of peek
        more[0] = ' ';
        line += more;
        ++ results;
    }
    return results;
}

void
trimComment( std::string& line )
{
    char quoted = '\0';
    std::string::iterator position = line.begin();
    while ( position != line.end() && (quoted != '\0' || *position == '#') ) {
        if ( *position == '\' && std::next( position ) != line.end() ) {
            ++ position;
        } else if ( *position == quoted ) {
            quoted = '\0';
        } else if ( *position == '\"' || *position == '\'' ) {
            quoted = *position;
        }
        ++ position;
    }
    line.erase( position, line.end() );
}

bool
isEmpty( std::string const& line )
{
    return std::all_of(
        line.begin(),
        line.end(),
        []( unsigned char ch ) { return isspace( ch ); } );
}

std::vector<Book>
parseFile( std::istream& source )
{
    std::vector<Book> results;
    int lineNumber = 0;
    std::string line;
    bool errorSeen = false;
    while ( std::getline( source, line ) ) {
        ++ lineNumber;
        int extraLines = getContinuationLines( source, line );
        trimComment( line );
        if ( ! isEmpty( line ) ) {
            static std::regex const matcher(
                R"^(([^,]*),\s*"([^"]*)"\s*\((\d+) pp.\)\s*\[(\S+)\s*([^\]]*)\])^"
                ); 
            std::smatch capture;
            if ( ! std::regex_match( line, capture, matcher ) ) {
                std::cerr << "Format error, line " << lineNumber << std::endl;
                errorSeen = true;
            } else {
                results.emplace_back(
                    capture[1],
                    capture[2],
                    std::stoi( capture[3] ),
                    capture[4],
                    capture[5] );
            }
        }
        lineNumber += extraLines;
    }
    if ( errorSeen ) {
        results.clear();    //  Or more likely, throw some sort of exception.
    }
    return results;
}
Run Code Online (Sandbox Code Playgroud)

这里真正的问题是你如何向调用者报告错误; 我怀疑在大多数情况下,异常是合适的,但根据用例,其他替代方案也可能有效.在这个例子中,我只返回一个空向量.(注释和延续线之间的相互作用可能也需要更好地定义,并根据它的定义进行修改.)

  • 什么是`std :: read`?您是否意味着[`std :: getline`](http://en.cppreference.com/w/cpp/string/basic_string/getline)? (6认同)