2009-04-13 45 views
1

我有一個看起來像這樣的數據:如何解析線,++領域的不同數目的用C

AAA 0.3 1.00 foo chr1,100 
AAC 0.1 2.00 bar chr2,33 
AAT 3.3 2.11  chr3,45 
AAG 1.3 3.11 qux chr1,88 
ACA 2.3 1.33  chr8,13 
ACT 2.3 7.00 bux chr5,122 

注意的是,上面的線是製表符分隔。而且, 它有時可能包含5個字段或4個字段。

我想要做的是捕獲變量中的第四個字段爲「」,如果它不包含任何值。

我有以下代碼,但不知何故它讀取第5個字段,第4個字段爲 ,當第4個爲空時。

什麼是正確的做法呢?

#include <iostream> 
#include <vector> 
#include <fstream> 
#include <sstream> 
using namespace std; 

int main (int arg_count, char *arg_vec[]) { 
    string line; 
    ifstream myfile (arg_vec[1]); 

    if (myfile.is_open()) 
    { 
     while (getline(myfile,line)) 
     { 
      stringstream ss(line);  
      string Tag; 
      double Val1; 
      double Val2; 
      double Field4; 
      double Field5; 

      ss >> Tag >> Val1 >> Val2 >> Field4 >> Field5; 
      cout << Field4 << endl; 
      //cout << Tag << "," << Val1 << "," << Val2 << "," << Field4 << "," << Field5 << endl; 

     } 
     myfile.close(); 
    } 
    else { cout << "Unable to open file"; } 
    return 0; 
} 
+0

你能多談談您的輸入?你有一種方法可以知道一條線何時會有4個場或5個場? – Tom 2009-04-13 06:40:55

+0

@Tom:最多可以包含5個字段,前3個字段總是存在。 – neversaint 2009-04-13 06:45:19

+0

鑑於您所展示的數據的性質,Field4和Field5預計會翻一番,這似乎很奇怪!由於系統不知道數據的劃分,所以當Field5爲空時,您將不得不將數據從Field4移動到Field5。答案是自動的是做到這一點。 – 2009-04-13 11:58:39

回答

4

另一個僅使用istream必須設置失敗位的事實的C++版本,如果操作符>>無法解析。

while(getline(ss, line)) 
{ 
    stringstream sl(line); 

    sl >> tag >> v1 >> v2 >> v3 >> v4; 

    if(sl.rdstate() == ios::failbit) // failed to parse 5 arguments? 
    { 
     sl.clear(); 
     sl.seekg(ios::beg); 
     sl >> tag >> v1 >> v2 >> v4; // do it again with 4 
     v3 = "EMPTY"; // just a default value 
    } 


    cout << "tag: " << tag <<std::endl 
     << "v1: " << v1 << std::endl 
     << "v2: " << v2 << std::endl 
     << "v3: " << v3 << std::endl 
     << "v4: " << v4 << std::endl << std::endl; 
} 
6

將行標記爲字符串的向量,然後根據標記的數量轉換爲適當的數據類型。

如果您可以使用Boost.Spirit,這可以簡化爲定義合適語法的簡單問題。

4

如果您想給Boost.Spirit一個嘗試,請從此開始。它編譯,我已經測試了一下。它似乎工作正常。

#include <iostream> 
#include <vector> 
#include <fstream> 
#include <sstream> 
#include <list> 
#include <boost/spirit/core.hpp> 
#include <boost/spirit/actor/assign_actor.hpp> 

using namespace std; 
using namespace boost::spirit; 

struct OneLine 
{ 
     string tag; 
     double val1; 
     double val2; 
     string field4; 
     string field5; 
}; 

int main (int arg_count, char *arg_vec[]) { 
    string line; 
    ifstream myfile (arg_vec[1]); 
    list<OneLine> myList; 

    if (myfile.is_open()) 
    { 
     while (getline(myfile,line)) 
     { 
       OneLine result; 
       rule<> good_p(alnum_p|punct_p); 
       parse(line.c_str(), 
        (*good_p)[assign_a(result.tag)] >> ch_p('\t') >> 
        real_p[assign_a(result.val1)] >> ch_p('\t') >> 
        real_p[assign_a(result.val2)] >> ch_p('\t') >> 
        (*good_p)[assign_a(result.field4)] >> ch_p('\t') >> 
        (*good_p)[assign_a(result.field5)], 
        ch_p(";")); 

       myList.push_back(result); 
     } 
     myfile.close(); 
    } 
    else { cout << "Unable to open file"; } 
    return 0; 
} 
1

最簡單的方式就是使用兩個調用FSCANF,scanf或sscanf的,像這樣:

std::string line = /* some line */; 
if(sscanf(line.c_str(), "%s %f %f %s", &str1, &float1, &float2, &str2) == 4){ 
    // 4 parameters 
}else if(sscanf(line.c_str(), ...) == 5){ 
    // 5 parameters 
} 

使用boost ::精神似乎矯枉過正,儘管這還不是最C++ - 是做事的方式。

2

有了提升:

int main() 
{ 
    std::ifstream in("parsefile.in"); 

    if (!in) 
     return 1; 

    typedef std::istreambuf_iterator<char> InputIterator; 
    typedef boost::char_separator<char> Separator; 
    typedef boost::tokenizer< Separator, InputIterator > Tokenizer; 

    Tokenizer tokens(InputIterator(in), 
        InputIterator(), 
        Separator(",\t\n", "", boost::keep_empty_tokens)); 

    const std::size_t columnsCount = 6; 
    std::size_t columnNumber = 1; 
    for(Tokenizer::iterator it = tokens.begin(); 
     it != tokens.end(); 
     ++it) 
    { 
     const std::string value = *it; 

     if (2 == columnNumber) 
     { 
      const double d = convertToDouble(value); 
     } 

     std::cout << std::setw(10) << value << "|"; 

     if (columnsCount == columnNumber) 
     { 
      std::cout << std::endl; 
      columnNumber = 1; 
     } 
     else 
     { 
      ++columnNumber; 
     } 
    } 

    return 0; 
} 

沒有提升:

int main() 
{ 
    std::ifstream in("parsefile.in"); 

    if (!in) 
     return 1; 

    const std::size_t columnNumber = 5; 
    while (in) 
    { 
     std::vector<std::string> columns(columnNumber); 

     for (std::size_t i = 0; i < columnNumber - 1; ++i) 
      std::getline(in, columns[i], '\t'); 
     std::getline(in, columns[columnNumber - 1], '\n'); 

     std::cout << columns[3] << std::endl; 
    } 

    return 0; 
} 

要轉換的字符串值加倍,你可以使用下面的。

double convertToDouble(const std::string& value) 
{ 
    std::stringstream os; 
    os << value; 
    double result; 
    os >> result; 
    return result; 
} 
1

又一個版本 - 我認爲這是一個涉及最少的輸入!

#include <iostream> 
#include <sstream> 
#include <string> 
using namespace std; 

int main() { 

    string f1, f4; 
    double f2, f3, f5; 

    string line; 
    istringstream is; 

    while(getline(cin, line)) { 

     is.str(line); 

     if (! (is >> f1 >> f2 >> f3 >> f4 >> f5)) { 
      is.str(line); 
      f4 = "*"; 
      is >> f1 >> f2 >> f3 >> f5; 
     } 

     cout << f1 << " " << f2 << " " << f3 << " " << f4 << " " << f5 << endl; 
    } 
} 
1

一個更通用的解決方案來讀取和處理任何基於文本的表。解決方案是提升。

typedef boost::function< void (int, int, const std::string&) > RecordHandler; 
void readTableFromFile(const std::string& fileName, 
         const std::string& delimiter, 
         RecordHandler handler); 

void handler(int row, int col, const std::string& value) 
{ 
    std::cout << "[ " << row << ", " << col << "] " << value; 
} 

int main() 
{ 
    readTableFromFile("parsefile.in", "\t,", handler); 

    return 0; 
} 

和實現

std::size_t columnsCountInTheFile(const std::string& fileName, 
            const std::string& delimiter) 
{ 
    typedef boost::char_separator<char> Separator; 
    typedef boost::tokenizer<Separator> Tokenizer; 

    std::ifstream in(fileName.c_str()); 

    std::string line; 
    std::getline(in, line); 

    Tokenizer t(line, 
       Separator(delimiter.c_str(), "", boost::keep_empty_tokens)); 

    return std::distance(t.begin(), t.end()); 
} 

void readTableFromFile(const std::string& fileName, 
         const std::string& delimiter, 
         RecordHandler handler); 
{ 
    std::ifstream in(fileName.c_str()); 

    if (!in) 
     throw std::runtime_error("can't read from " + fileName); 

    typedef std::istreambuf_iterator<char> InputIterator; 
    typedef boost::char_separator<char> Separator; 
    typedef boost::tokenizer< Separator, InputIterator > Tokenizer; 

    Tokenizer tokens(InputIterator(in), 
        InputIterator(), 
        Separator((delimiter + "\n").c_str(), "", boost::keep_empty_tokens)); 

    const std::size_t columnsCount = columnsCountInTheFile(fileName, delimiter); 

    std::size_t columnNumber = 1; 
    std::size_t rowNumber = 1; 
    for(Tokenizer::iterator it = tokens.begin(); 
     it != tokens.end(); 
     ++it) 
    { 
     handler(rowNumber, columnNumber, *it); 

     if (columnsCount == columnNumber) 
     { 
      columnNumber = 1; 
      ++rowNumber; 
     } 
     else 
     { 
      ++columnNumber; 
     } 
    } 
}