2009-04-13 4 views
1

Ich habe ein Datum, die wie folgt aussieht:Wie Linien mit unterschiedlicher Anzahl der Felder in C++ Parst

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 

Beachten Sie, dass die oben genannten Linien sind Tab getrennt. Darüber hinaus kann es manchmal 5 Felder oder 4 Felder enthalten.

Was ich tun möchte, ist 4. Felder in Variable als "" zu erfassen, wenn es keinen Wert enthält.

Ich habe die folgenden Codes, aber irgendwie liest es die 5. Felder, als 4. Felder wenn 4. ist leer.

Was ist der richtige Weg?

#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

Sie mehr über Ihre Eingabe sagen? Haben Sie eine Möglichkeit zu wissen, wann eine Linie 4 Felder oder 5 Felder haben wird? – Tom

+0

@ Tom: Allenfalls kann es 5 Felder enthalten und ersten 3 Felder existieren immer. – neversaint

+0

Es scheint seltsam, dass Field4 und Field5 erwartet das Doppelte der Art der Daten gegeben werden Ihnen zeigen!Da das System Ihre Aufteilung der Daten nicht kennt, müssen Sie die Daten von Feld4 nach Feld5 verschieben, wenn Feld5 leer ist. Die Antworten geben automatisch war, das zu tun. –

Antwort

4

Eine andere C++ - Version, die nur die Tatsache verwendet, dass istream das Failbit setzen muss, wenn der Operator >> nicht parsen kann.

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

Es ist schwer zu ändern, wenn zwei von ihnen Null sein können. –

+0

@Mykola: Richtig, ich versuche immer noch herauszufinden, wie man eine Art Streammanipulator, der optionale Parameter erlaubt, sauber erstellt. –

6

tokenize der Zeile in einen Vektor von Strings und dann an einen geeigneten Datentyp in Abhängigkeit von der Anzahl von Token tun Konvertierung.

Wenn Sie Boost.Spirit verwenden können, reduziert dies auf ein einfaches Problem der Definition einer geeigneten Grammatik.

+0

Warum wurde das abgelehnt? Es ist die richtige Antwort, soweit ich das beurteilen kann. – Niki

+0

Sie können boost :: tokenizer für Option 1 verwenden. –

+0

@ribeas: Guter Punkt. Ich wollte speziell Spirit erwähnen, da ich dieses hier auf SO nicht viel gesehen habe :) – dirkgently

4

Wenn Sie Boost.Spirit ausprobieren möchten, beginnen Sie damit. Es kompiliert und ich habe es ein bisschen getestet. Es scheint gut zu funktionieren.

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

+1 Danke für den Boost Geist Beispiel. –

1

Die einfachste Sache ist nur zwei Anrufe zu verwenden, um fscanf, scanf oder sscanf wie so:

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 
} 

Mit boost :: Geist wie übertrieben scheint, obwohl diese C++ nicht das ist - meine Art, Dinge zu tun.

+0

Der Benutzer möchte in eine std :: string lesen - das können Sie nicht mit scanf() tun –

+0

Sie können mit sscanf, die eine const char * –

+0

Das Beispiel aktualisiert, um das zu zeigen. –

2

Mit boost:

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

Ohne boost:

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

String-Wert konvertieren zu verdoppeln können Sie die folgenden.

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

Noch eine andere Version - ich denke, das ist die, die am wenigsten Tipparbeit beinhaltet!

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

Operator!() Scheitert auch an badbit, die Sie vielleicht nicht wollen :-). Ansonsten ist es meiner vorherigen Lösung ziemlich ähnlich. –

1

Eine weitere generische Lösung zum Lesen und Verarbeiten einer textbasierten Tabelle. Lösung ist mit Schub.

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

und die Umsetzung

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