C++:解析带有括号的数字字符串

Ale*_*tke 3 c++ stringstream c++11

这似乎微不足道,但我似乎无法解决这个问题.我有格式的STL字符串2013 336 (02 DEC) 04(04小时在哪里,但这是无关紧要的).我想提取月份的日期(02在示例中)和月份以及小时.

我试图干净利落地避免例如在括号中拆分字符串,然后使用子字符串等.理想情况下,我想使用a stringstream并将其重定向到变量.我现在得到的代码是:

int year, dayOfYear, day;
std::string month, leftParenthesis, rightParenthesis;
std::string ExampleString = "2013 336 (02 DEC) 04";

std::istringstream yearDayMonthHourStringStream( ExampleString );
yearDayMonthHourStringStream >> year >> dayOfYear >> leftParenthesis >> day >> month >> rightParenthesis >> hour;
Run Code Online (Sandbox Code Playgroud)

它提取year并且dayOfYear好了2013,336但事情开始变得糟糕.day0,month和空字符串,和hour843076624.

leftParenthesis(02这样它包含day但当我尝试省略leftParenthesis变量时,重定向yearDayMonthHourStringStreamday也是0.

关于如何处理这个问题的任何想法?我不知道正则表达式(但是),不可否认,我不知道我现在是否能够学会它们(时间).

编辑 好的,我知道了.虽然这就像我可以用正则表达式让生活变得那么容易的第十亿次,所以我想是时候了.无论如何,有用的是:

int year, dayOfYear, day, month, hour, minute, revolution;
std::string dayString, monthString;

yearDayMonthHourStringStream >> year >> dayOfYear >> dayString >> monthString >> hour;
std::string::size_type sz;
day = std::stod( dayString.substr( dayString.find("(")+1 ), &sz ); // Convert day to a number using C++11 standard. Ignore the ( that may be at the beginning.
Run Code Online (Sandbox Code Playgroud)

这仍然需要处理monthString,但我需要将其改为数字,所以这不是一个巨大的劣势.不是你能做的最好的事情(正则表达式)但是工作并且不是太脏.据我所知,也是模糊的便携式,希望不会停止使用新的编译器.但是,谢谢大家.

Jam*_*nze 7

显而易见的解决方案使用正则表达式( std::regex在C++ 11或boost::regexC++ 11之前).只需捕获您感兴趣的群组,并std::istringstream在必要时使用 它们进行转换.在这种情况下,

std::regex re( "\\s*\\d+\\s+\\d+\\s*\\((\\d+)\\s+([[:alpha:]]+))\\s*(\\d+)" );
Run Code Online (Sandbox Code Playgroud)

应该做的伎俩.

正则表达式非常简单; 学习它们所花费的时间比实施任何替代解决方案要少.

对于替代解决方案,您可能希望逐个字符地读取行,将其分解为标记.沿线的东西:

std::vector<std::string> tokens;
std::string currentToken;
char ch;
while ( source.get(ch) && ch != '\n' ) {
    if ( std::isspace( static_cast<unsigned char>( ch ) ) ) {
        if ( !currentToken.empty() ) {
            tokens.push_back( currentToken );
            currentToken = "";
        }
    } else if ( std::ispunct( static_cast<unsigned char>( ch ) ) ) {
        if ( !currentToken.empty() ) {
            tokens.push_back( currentToken );
            currentToken = "";
        }
        currentToken.push_back( ch );
    } else if ( std::isalnum( static_cast<unsigned char>( ch ) ) ) {
        currentToken.push_back( ch );
    } else {
        //  Error: illegal character in line.  You'll probably
        //  want to throw an exception.
    }
}
if ( !currentToken.empty() ) {
    tokens.push_back( currentToken );
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,一系列字母数字字符是一个标记,任何单个标点字符也是如此.您可以更进一步,确保令牌全部为alpha或所有数字,并且可能重新组合标点符号序列,但这似乎足以解决您的问题.

获得令牌列表后,您可以进行任何必要的验证(在正确的位置括号等),并转换您感兴趣的令牌,如果他们需要转换.

编辑:

FWIW:我一直在尝试使用auto加上lambda作为定义嵌套函数的方法.我的思绪并没有弥补是否是一个好主意:我并不总能找到可读的结果.但在这种情况下:

auto pushToken = [&]() {
    if ( !currentToken.empty() ) {
        tokens.push_back( currentToken );
        currentToken = "";
    }
}
Run Code Online (Sandbox Code Playgroud)

就在循环之前,则更换所有的ifpushToken().(或者你可以创建一个数据结构 tokens,currentTokenpushToken成员函数.这工作,即使在预C++ 11).

编辑:

最后一句话,因为OP似乎只想用这个来做std::istream:解决方案就是添加一个MustMatch操纵器:

class MustMatch
{
    char m_toMatch;
public:
    MustMatch( char toMatch ) : m_toMatch( toMatch ) {}
    friend std::istream& operator>>( std::istream& source, MustMatch const& manip )
    {
        char next;
        source >> next;
        //  or source.get( next ) if you don't want to skip whitespace.
        if ( source && next != m_toMatch ) {
            source.setstate( std::ios_base::failbit );
        }
        return source;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如@Angew指出的那样,你>>几个月也需要一个; 通常,几个月会被表示为一个类,所以你要重载>>:

std::istream& operator>>( std::istream& source, Month& object )
{
    //      The sentry takes care of skipping whitespace, etc.
    std::ostream::sentry guard( source );
    if ( guard ) {
        std::streambuf* sb = source.rd();
        std::string monthName;
        while ( std::isalpha( sb->sgetc() ) ) {
            monthName += sb->sbumpc();
        }
        if ( !isLegalMonthName( monthName ) ) {
            source.setstate( std::ios_base::failbit );
        } else {
            object = Month( monthName );
        }
    }
    return source;
}
Run Code Online (Sandbox Code Playgroud)

当然,您可以在此处引入许多变体:月份名称可以限制为最多3个字符,例如(通过制作循环条件monthName.size() < 3 && std::isalpha( sb->sgetc() )).但是,如果你有个以任何方式在你的代码打交道,写一个Month类及其 >><<运营商是你必须做的反正迟早.

然后像:

source >> year >> dayOfYear >> MustMatch( '(' ) >> day >> month
       >> MustMatch( ')' ) >> hour;
if ( !(source >> ws) || source.get() != EOF ) {
    //  Format error...
}
Run Code Online (Sandbox Code Playgroud)

就是这一切.(像这样使用操纵器是另一种值得学习的技术.)