2008-11-06 5 views
8

J'ai un fichier contenant des données énumérées comme suit:C++ fichier io et division par le séparateur

0,  2, 10 
10,  8, 10 
10,  10, 10 
10,  16, 10 
15,  10, 16 
17,  10, 16 

Je veux être en mesure de saisir le fichier et divisé en trois tableaux, dans le processus de coupe tous les espaces excédentaires et convertir chaque élément en entiers.

Pour une raison quelconque, je ne peux pas trouver un moyen facile de le faire en C++. Le seul succès que j'ai eu est en entrant chaque ligne dans un tableau, puis en regexing tous les espaces et ensuite le diviser. Tout ce processus m'a pris un bon 20-30 lignes de code et c'est une douleur à modifier pour dire un autre séparateur (par exemple espace), etc

Ceci est l'équivalent python de ce que je voudrais avoir en C++: La facilité d'utilisation de faire cela en python est l'une des raisons pour lesquelles je me suis déplacé à lui en premier lieu. Cependant, j'ai besoin de le faire en C++ maintenant et je détesterais avoir à utiliser mon code de ligne moche 20-30.

Toute aide serait appréciée, merci!

Répondre

4

Il n'y a vraiment rien de mal avec fscanf, qui est probablement la solution la plus rapide dans ce cas. Et il est aussi court et lisible que le code python:

FILE *fp = fopen("file.dat", "r"); 
int x, y, z; 
std::vector<int> vx, vy, vz; 

while (fscanf(fp, "%d, %d, %d", &x, &y, &z) == 3) { 
    vx.push_back(x); 
    vy.push_back(y); 
    vz.push_back(z); 
} 
fclose(fp); 
+0

Beau travail. Les gens oublient la correspondance de modèle de scanf. La solution la plus simple est la meilleure. – jbruni

2

Quelque chose comme:

vector<int> inint; 
vector<int> inbase; 
vector<int> outbase; 
while (fgets(buf, fh)) { 
    char *tok = strtok(buf, ", "); 
    inint.push_back(atoi(tok)); 
    tok = strtok(NULL, ", "); 
    inbase.push_back(atoi(tok)); 
    tok = strtok(NULL, ", "); 
    outbase.push_back(atoi(tok)); 
} 

Sauf erreur de vérification.

+0

j'éviter une telle solution « C-ish » pour, eh bien, l'esthétique ... mais surtout dans ce cas parce que strtok a de sérieux problèmes thread-safe. Corrigez le code si! – MattyT

1

std :: getline vous permet de lire une ligne de texte, et vous pouvez utiliser un flux de chaîne pour analyser la ligne individuelle:

string buf; 
getline(cin, buf); 
stringstream par(buf); 

char buf2[512]; 
par.getline(buf2, 512, ','); /* Reads until the first token. */ 

Une fois que vous obtenez la ligne de texte dans la chaîne, vous pouvez utiliser réellement toute fonction d'analyse que vous voulez, même sscanf (buf.c_str(), "% d,% d '% d", & i1, & i2, & i3), en utilisant atoi sur la sous-chaîne avec l'entier, ou par une autre méthode.

Vous pouvez également ignorer les caractères indésirables dans le flux d'entrée, si vous savez qu'ils sont là:

if (cin.peek() == ',') 
    cin.ignore(1, ','); 
cin >> nextInt; 
1

Si vous ne me dérange pas utiliser les bibliothèques Boost ...

#include <string> 
#include <vector> 
#include <boost/lexical_cast.hpp> 
#include <boost/regex.hpp> 

std::vector<int> ParseFile(std::istream& in) { 
    const boost::regex cItemPattern(" *([0-9]+),?"); 
    std::vector<int> return_value; 

    std::string line; 
    while (std::getline(in, line)) { 
     string::const_iterator b=line.begin(), e=line.end(); 
     boost::smatch match; 
     while (b!=e && boost::regex_search(b, e, match, cItemPattern)) { 
      return_value.push_back(boost::lexical_cast<int>(match[1].str())); 
      b=match[0].second; 
     }; 
    }; 

    return return_value; 
} 

Cela extrait les lignes du flux, puis utilise la bibliothèque Boost :: RegEx (avec un groupe de capture) pour extraire chaque nombre des lignes. Il ignore automatiquement tout ce qui n'est pas un nombre valide, bien que cela puisse être changé si vous le souhaitez.

Il s'agit toujours d'une vingtaine de lignes avec le #include s, mais vous pouvez l'utiliser pour extraire essentiellement n'importe quoi à partir des lignes du fichier. C'est un exemple trivial, j'utilise du code à peu près identique pour extraire des balises et des valeurs optionnelles d'un champ de base de données, la seule différence majeure est l'expression régulière.

EDIT: Oups, vous vouliez trois vecteurs distincts.Essayez cette légère modification à la place:

const boost::regex cItemPattern(" *([0-9]+), *([0-9]+), *([0-9]+)"); 
std::vector<int> vector1, vector2, vector3; 

std::string line; 
while (std::getline(in, line)) { 
    string::const_iterator b=line.begin(), e=line.end(); 
    boost::smatch match; 
    while (b!=e && boost::regex_search(b, e, match, cItemPattern)) { 
     vector1.push_back(boost::lexical_cast<int>(match[1].str())); 
     vector2.push_back(boost::lexical_cast<int>(match[2].str())); 
     vector3.push_back(boost::lexical_cast<int>(match[3].str())); 
     b=match[0].second; 
    }; 
}; 
6

Il n'y a pas vraiment besoin d'utiliser boost dans cet exemple que les flux feront bien l'affaire:

int main(int argc, char* argv[]) 
{ 
    ifstream file(argv[1]); 

    const unsigned maxIgnore = 10; 
    const int delim = ','; 
    int x,y,z; 

    vector<int> vecx, vecy, vecz; 

    while (file) 
    { 
     file >> x; 
     file.ignore(maxIgnore, delim); 
     file >> y; 
     file.ignore(maxIgnore, delim); 
     file >> z; 

     vecx.push_back(x); 
     vecy.push_back(y); 
     vecz.push_back(z); 
    } 
} 

Bien que si je devais utiliser boost je préfère la simplicité de tokenizer à regex ... :)

1

pourquoi ne pas le même code que en python :)?

std::ifstream file("input_hard.dat"); 
std::vector<int> inint, inbase, outbase; 

while (file.good()){ 
    int val1, val2, val3; 
    char delim; 
    file >> val1 >> delim >> val2 >> delim >> val3; 

    inint.push_back(val1); 
    inbase.push_back(val2); 
    outbase.push_back(val3); 
} 
0

Si vous voulez être en mesure à l'échelle des formats d'entrée plus difficile, vous devriez considérer l'esprit, analyseur boost bibliothèque combinateur.

This page a un exemple qui ne presque ce que vous avez besoin (avec reals et un vecteur bien)