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

时间:2014-10-09 08:49:59

标签: c++ c++11 stringstream

这似乎微不足道,但我似乎无法解决这个问题。我有格式2013 336 (02 DEC) 04的STL字符串(其中04是小时,但那是无关紧要的)。我想提取月中的某一天(示例中为02)以及月份和小时。

我正试图干净利落地避免在括号中拆分字符串,然后使用子字符串等。理想情况下,我想使用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;

它将yeardayOfYear提取为2013336,但事情开始变得糟糕。 day0month为空字符串,hour为843076624。

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.

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

5 个答案:

答案 0 :(得分:7)

显而易见的解决方案 使用正则表达式(或者 {C} 11中的std::regex或C ++ 11中的boost::regex。只是 捕获您感兴趣和使用的群组 如果需要,std::istringstream可以转换它们。在这 情况下,

std::regex re( "\\s*\\d+\\s+\\d+\\s*\\((\\d+)\\s+([[:alpha:]]+))\\s*(\\d+)" );

应该做的伎俩。

正则表达式非常简单;它需要 你学习它们的时间少于实施任何替代方案 溶液

对于替代解决方案,您可能希望阅读 按字符排列,将其分解为标记。某物 沿着这条线:

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 );
}

在这种情况下,一系列字母数字字符是一个标记, 就像任何一个标点字符一样。你可以走得更远, 确保令牌是全部字母或全部数字,以及 也许重新组合标点符号序列,但这似乎是 足以解决你的问题了。

一旦获得了令牌列表,您就可以做任何必要的事情 验证(在适当的地方括号等),和 如果需要,转换您感兴趣的令牌 转换。

编辑:

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

auto pushToken = [&]() {
    if ( !currentToken.empty() ) {
        tokens.push_back( currentToken );
        currentToken = "";
    }
}

在循环之前,然后用{替换所有if pushToken()。 (或者您可以创建一个数据结构 tokenscurrentTokenpushToken成员函数。 这甚至可以在pre-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;
    }
}

正如@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;
}

当然,你可以在这里介绍很多变种:月份 例如,name可以限制为最多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...
}

就是所需要的。 (像这样的操纵者的使用是 另一种值得学习的技巧。)

答案 1 :(得分:3)

scanf()的@Angew +1。它将在一行中完成您想要的任务:

int day;
int hour;
char month[4];
int result = sscanf(ExampleString.c_str(), "%*d %*d (%d %3s) %d", &day, month, &hour);
if (result != 3)
{
    // parse error;
}

答案 2 :(得分:2)

正则表达式http://coliru.stacked-crooked.com/a/ac5a4c9269e94344

的工作示例

(不包括字符串解析)

#include <iostream>
#include <regex>
#include <string>
using namespace std;
int main()
{
    //int year, dayOfYear, day;
    //std::string month, leftParenthesis, rightParenthesis;
    std::string ExampleString = "2013 336 (02 DEC) 04";
    regex pattern("\\s*(\\d+)\\s+(\\d+)\\s*\\((\\d+)\\s+([[:alpha:]]+)\\)\\s*(\\d+)\\s*");

    // Matching single string
    std::smatch sm;
    if (std::regex_match(ExampleString, sm, pattern)) {
        cout << "year: " << sm[1].str() << endl;
        cout << "dayOfYear: " << sm[2].str() << endl;
        cout << "day: " << sm[3].str() << endl;
        cout << "month: " << sm[4].str() << endl;
        cout << "hour: " << sm[5].str() << endl;
    }

    cout << endl;
    cout << endl;

    // If your data contains multiple lines to parse, use this version
    // unfortunately it will skip all lines that does not match pattern.
    ExampleString = "2013 336 (02 DEC) 04" "\n2014 336 (02 DEC) 04" "\n2015 336 (02 DEC) 04";
    for (sregex_iterator it(ExampleString.begin(), ExampleString.end(), pattern), end_it;
        it != end_it; ++it)
    {
        cout << "year: " << (*it)[1].str() << endl;
        cout << "dayOfYear: " << (*it)[2].str() << endl;
        cout << "day: " << (*it)[3].str() << endl;
        cout << "month: " << (*it)[4].str() << endl;
        cout << "hour: " << (*it)[5].str() << endl;
        cout << endl;
    }
}

下面是debuggex,它不接受[[:alpha:]]所以用\ w代替它,虽然[a-zA-Z]会更好:

\s*(\d+)\s+(\d+)\s*\((\d+)\s+(\w+)\)\s*(\d+)\s*

Regular expression visualization

Debuggex Demo

答案 3 :(得分:1)

如果你真的不想使用正则表达式,并且你想要一个看起来与你已经拥有的相似的hack ...你可以用空格替换字符串中的括号。 (我并不是说这是一个好的解决方案,但它值得了解。)

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

std::replace_if(ExampleString.begin(), ExampleString.end(), [](char c) { return c == '(' || c == ')'; }, ' ');

std::istringstream yearDayMonthHourStringStream( ExampleString );
yearDayMonthHourStringStream >> year >> dayOfYear >> day >> month >> hour;

答案 4 :(得分:1)

FWIW,您可以通过将左括号和右括号读入char变量而不是字符串来使流方法工作,并且当它看到右括号时让month解析停止。但是有点难看:

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

std::istringstream yearDayMonthHourStringStream( ExampleString );
if (yearDayMonthHourStringStream >> year >> dayOfYear >> leftParenthesis
        >> day >> std::ws &&
    getline(yearDayMonthHourStringStream, month, ')') &&
    yearDayMonthHourStringStream >> rightParenthesis >> hour &&
    leftParenthesis == '(' && rightParenthesis == ')')
    ...use your variables...
else
    ...report bad input...

<iomanip>'s std::ws刚刚使用,因此ws的容差始终保持一致。