2014-10-09 3 views
3

이것은 사소한 것처럼 보이지만이 문제를 해결할 수는 없습니다. STL 문자열이 2013 336 (02 DEC) 04 (여기서 04은 시간이지만 그와는 관련이 없습니다)입니다. 나는 그 달의 날짜 (예제에서는 02)와 그 달뿐만 아니라 시간을 추출하고 싶다.C++ : 괄호로 묶인 숫자 문자열을 구문 분석

나는 이것을 깨끗하게하려고 노력하고 있습니다. 예를 들어 피하십시오. 괄호에서 문자열을 분할 한 다음 부분 문자열 등을 사용하여 작업하십시오. 이상적으로는 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; 

그것은 year 다음 2013336하지만 같은 괜찮아 dayOfYear 일이 잘못가는 시작 추출합니다. day는 843076624.

leftParenthesis(02 그래서는 day을 포함하지만 yearDayMonthHourStringStream 스트림을 리디렉션 동안 내가 leftParenthesis 변수를 생략 할 때 day0입니다 0, month과 빈 문자열 및 hour입니다.

이 문제를 해결하는 방법에 대한 아이디어가 있으십니까? 나는 정규 표현을 (아직) 알지 못하지만, 지금 당장 (시간적으로) 배울 여력이 있는지 확실하지 않습니다.

수정 그래, 알겠습니다. 이것은 정규 표현식으로 내 삶을 훨씬 더 쉽게 만들 수있는 10 억 번째 시간과 같지만, 그래서 나는 그것이 시간이라고 생각한다. 어쨌든, 어떤 일을하는 것은이었다

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의 처리를 필요로하지만, 그 큰 단점이되지 않도록 나는 어쨌든 번호로 변경해야합니다. 당신이 할 수있는 최선의 일 (정규 표현식)이 아니라 작동하고 너무 지저분하지 않습니다. 필자가 아는 한은 막연하게 이식성이 있으며 새로운 컴파일러로 작업하는 것을 멈추지 않을 것입니다. 하지만 모두에게 감사드립니다.

+0

'leftParenthesis'에서 첫 번째 문자를 버려서 원하는대로 만들 수 있습니다. – JoriO

+1

이것은'* scanf'가 스트림과 비교하여 더 나은 작업을 수행하는 몇 안되는 경우 중 하나입니다. 왜냐하면 입력뿐만 아니라 일치도 수행 할 수 있기 때문입니다. – Angew

+0

부스트를 사용하고 싶은지 확실하지 않지만 Boost.Spirit은이를위한 완벽한 도구입니다. [=> 괄호가있는 예제 구문 분석기] (http://www.boost.org/doc/libs/1_56_0/libs/spirit/doc/html/spirit/qi/tutorials/complex___our_first_complex_parser.html) – leemes

답변

7

명백한 용액 는 (C++ 11 또는 C++ boost::regex 사전 (11)에, 어느 std::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 플러스 로 람다 중첩 된 함수를 정의하는 방법을 사용하여 실험을했습니다. 좋은 생각인지 아닌지 내 마음은 으로 작성되지 않았습니다. 항상 읽을 수있는 결과가 인 것은 아닙니다. 그러나이 경우 :

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

그냥 루프 전에, 다음 pushToken()으로 if을 모두 교체합니다. (아니면 tokens, currentToken와 데이터 구조와 pushToken 멤버 함수를 만들 수 을이도 사전 C++ 11에서 작동합니다..)

편집 :

마지막으로 발언을, 영업 이익 때문에 std::istream 독점적으로이 을 수행 할 것으로 보인다 :

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가 지적한 것처럼, 당신이 '솔루션은 MustMatch 조작을 추가 에있을 것입니다 또한 월에 >>이 필요합니다. 일반적으로, 달은 클래스로 표현 될 것이다, 그래서 이에 >>에 과부하를 줄 :

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

당신은, 물론, 여기에 많은 변종을 소개 할 수 : 월 이름이 셋의 최대 제한 될 수 있습니다 문자 (예 : ) (루프 조건을 monthName.size() < 3 && std::isalpha(sb->sgetc())으로 지정). 그러나 코드에서 어떤 식 으로든 개월을 다루는 경우 Month 클래스와 해당 및 << 연산자를 작성하는 것은 늦어도 나중에해야 할 일입니다.

는 다음과 같은 :

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

모든 것을이 필요하다. (이 같은 manipulators의 사용은 학습할만한 또 다른 기술입니다.) 정규식에 대한 예를 들어 작업

+0

원시 문자열 리터럴이 조금 더 명확하게 표시합니까? – user657267

+0

@James right, 나는 네가 옳다고 생각한다. 나는 이것을 너무 오랫동안 퍼뜨릴 수도있다. 정규 표현식에 대한 나의 태도는 도넛, 피자, 담배를 피우는 사람들과 똑같습니다. 매일 아침마다 달리지 마십시오. 사람들은 옳은 일이 무엇인지 알고 있지만 우리는 (보통) 결코하지 않습니다 : P –

+0

@ user657267 확실히 . 그러나 pre-C++ 11을 사용하고 있다면 boost :: regex를 대신 쓸 수 있도록 작성하려고했습니다. 하지만 어쩌면 원시 문자열 리터럴을 선호했을 것입니다. 왜냐하면이 정규 표현식이 얼마나 단순한지를 명확히하기 때문입니다. 순전히 선형이며 정규 표현식 연산자가 전혀 필요하지 않습니다. 문자 클래스에 대한 특별한 이름. 따라서 정규 표현식을 배우기 시작하는 좋은 방법이 될 수 있습니다. –

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

는, 그래서 그 승 \로 대체 [[:alpha:]]를 허용하지 않습니다, debuggex입니다) 포함되지 [A-zA-하지만 Z] 더 나은 것 :

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

Regular expression visualization

Debuggex Demo

+0

나는 당신이 ' t는 내가 정규식 시각화를 추가 마음. :) – leemes

+0

그 달에'\\ w +'대신'[[: alpha :]]'를 사용하면 요구 사항을 더 가깝게 맞출 수 있습니다. (다른 한편으로, 일단 그것을 잡았 으면 문자열을 검사하는 것만으로도 충분히 쉽다.) –

+0

@leemes no, thanks – marcinj

1

정말로 정규식을 사용하고 싶지 않고 이미 가지고있는 것과 비슷한 모양의 해킹을 원한다면 ... 문자열의 괄호를 공백으로 바꿀 수 있습니다. (나는 이것이 좋은 해결책이 말하는 게 아니에요,하지만에 대해 아는 가치가있다.) scanf()에 대한

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; 
+0

OP와 패턴이 일치하지 않는 문자열을 거부해야하는 경우가 아니라면 아주 좋은 해결책입니다. 그러나 나는 그가 그것을 필요로하지 않는다고 생각한다. 그래서 당신의 대답은 제 의견으로는 괜찮습니다. – leemes

3

@Angew 일을. 대신 문자열의 char 변수로 왼쪽 및 오른쪽 괄호를 읽기, 정지 할 때를 구문 분석 month을함으로써

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; 
} 
+0

+1을 다시하고 그 (또는 다른 누구)가'char month [4]'를'std :: string month;'로 변경합니다. 'scanf '를 사용하는 것은 전문 코드에서 유효하지 않습니다. 왜냐하면 그것은 유지할 수 없기 때문입니다. –

+0

@JamesKanze 당신은 심각 할 수 없습니다. 그런 실수를 저지르기에 어리석은 프로그래머라면 프로그래머로서 오래 머물지 않을 것이다. – dgnuff

+0

분명히, 당신은 실생활 개발에 대한 경험이 없습니다. 고도로 숙련 된 개발자조차도 항상 그런 실수를합니다. 그래서 'scanf'와 같은 기능은 일반적으로 전문적인 환경에서 금지되어 있습니다. –

1

FWIW, 당신은 스트림 방식의 작업을 할 수 있습니다 : 그것은 당신이 한 줄에 원하는 것을 할 것입니다 ... 오른쪽 괄호를보고하지만 추한 비트를 가져옵니다

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... 

(ws에 대한 허용 오차는 전체에 일관성이 있도록 <iomanip> 'SS std::ws 그냥 사용).

관련 문제