2009-02-11 11 views
19

J'aime comment en python que je peux faire quelque chose comme:C analyse syntaxique de la chaîne de (style python)

points = [] 
for line in open("data.txt"): 
    a,b,c = map(float, line.split(',')) 
    points += [(a,b,c)] 

Fondamentalement, il est la lecture d'une liste de lignes où chacun représente un point dans l'espace 3D, le point est représenté comme trois nombres séparés par des virgules

Comment cela peut-il être fait en C++ sans trop de maux de tête?

La performance n'est pas très importante, cette analyse ne se produit qu'une seule fois, donc la simplicité est plus importante.

P.S. Je sais que cela ressemble à une question de débutant, mais croyez-moi j'ai écrit un lexer en D (à peu près C++) qui consiste à lire du texte char par char et reconnaître des jetons, c'est juste revenir à C++ après un longue période de python, ça ne me donne pas envie de perdre mon temps avec de telles choses.

+18

Que diriez-vous des exemples de ce qui suit, ils sont un peu python -esq: http: //www.cod eproject.com/KB/recipes/Tokenizer.aspx De plus, ils sont très efficaces et plutôt élégants. –

Répondre

24

`d faire quelque chose comme ceci:

ifstream f("data.txt"); 
string str; 
while (getline(f, str)) { 
    Point p; 
    sscanf(str.c_str(), "%f, %f, %f\n", &p.x, &p.y, &p.z); 
    points.push_back(p); 
} 

x, y, z doit être flotteurs.

et comprennent:

#include <iostream> 
#include <fstream> 
+2

Si vous décidez de passer des flottants aux doubles, n'oubliez pas de changer chaque% f en% lf. Une solution utilisant l'opérateur >>() au lieu de sscanf() n'a pas besoin d'être modifiée dans ce cas. –

+0

J'ai accepté cette réponse pour la brièveté et la droiture :) – hasen

3

Vous pouvez lire le fichier depuis une ligne std :: iostream ligne par ligne, placer chaque ligne dans une chaîne std :: puis utiliser boost :: tokenizer pour la diviser. Il ne sera pas tout à fait aussi élégant/court que le python un mais beaucoup plus facile que la lecture des choses dans un caractère à la fois ...

+0

boost :: tokenizer est vraiment bon :) –

14
#include <iostream> 
#include <fstream> 
#include <sstream> 
#include <string> 
#include <vector> 
#include <algorithm>  // For replace() 

using namespace std; 

struct Point { 
    double a, b, c; 
}; 

int main(int argc, char **argv) { 
    vector<Point> points; 

    ifstream f("data.txt"); 

    string str; 
    while (getline(f, str)) { 
     replace(str.begin(), str.end(), ',', ' '); 
     istringstream iss(str); 
     Point p; 
     iss >> p.a >> p.b >> p.c; 
     points.push_back(p); 
    } 

    // Do something with points... 

    return 0; 
} 
+1

inefficace mais +1 pour le bon style –

+0

@Iraimbilanja: Bien que je traverse la chaîne deux fois (d'abord en utilisant replace(), puis via iss), je pense que c'est au moins aussi rapide en pratique comme les autres solutions, à l'exception possible de l'approche basée sur sscanf() de klew. Les processeurs sont bons sur replace(). –

7

Cette réponse est basée sur la réponse précédente par j_random_hacker et utilise de Boost Spirit.

#include <iostream> 
#include <fstream> 
#include <sstream> 
#include <string> 
#include <boost/spirit.hpp> 

using namespace std; 
using namespace boost; 
using namespace boost::spirit; 

struct Point { 
    double a, b, c; 
}; 

int main(int argc, char **argv) 
{ 
    vector<Point> points; 

    ifstream f("data.txt"); 

    string str; 
    Point p; 
    rule<> point_p = 
      double_p[assign_a(p.a)] >> ',' 
     >> double_p[assign_a(p.b)] >> ',' 
     >> double_p[assign_a(p.c)] ; 

    while (getline(f, str)) 
    { 
     parse(str, point_p, space_p); 
     points.push_back(p); 
    } 

    // Do something with points... 

    return 0; 
} 
+1

Est-ce que tout downvoter soin d'expliquer le -1? –

+2

Peut-être parce que l'utilisation de boost :: spirit pour analyser les listes séparées par des virgules est une surcharge? Boost :: spirit affecte significativement le temps de compilation. – JBeurer

+1

Peut-être en raison du fait que vous instanciez la règle à l'intérieur de la boucle, typiquement ce serait une énorme source d'inefficacité, vous feriez mieux de l'avoir défini en dehors de la boucle. - L'esprit est exagéré, ajoute une énorme quantité de temps de compilation, et est presque impossible à déboguer, les avertissements du compilateur et les messages d'erreur sont simplement incompréhensibles. –

16

Tous ces bons exemples de côté, en C++ vous normalement remplacer le operator >> pour votre type de point pour atteindre quelque chose comme ceci:

point p; 
while (file >> p) 
    points.push_back(p); 

ou même:

copy(
    istream_iterator<point>(file), 
    istream_iterator<point>(), 
    back_inserter(points) 
); 

La mise en œuvre pertinente de l'opérateur pourrait ressembler beaucoup comme le code par j_random_hacker.

+0

C'est certainement la manière de le faire si vous entrerez des objets de Point dans plusieurs endroits différents dans votre code. –

+1

Whoa. Downvoted? Qu'est-ce que le f ... pour? –

4

Fun avec Boost.Tuples:

#include <boost/tuple/tuple_io.hpp> 
#include <vector> 
#include <fstream> 
#include <iostream> 
#include <algorithm> 

int main() { 
    using namespace boost::tuples; 
    typedef boost::tuple<float,float,float> PointT; 

    std::ifstream f("input.txt"); 
    f >> set_open(' ') >> set_close(' ') >> set_delimiter(','); 

    std::vector<PointT> v; 

    std::copy(std::istream_iterator<PointT>(f), std::istream_iterator<PointT>(), 
      std::back_inserter(v) 
    ); 

    std::copy(v.begin(), v.end(), 
       std::ostream_iterator<PointT>(std::cout) 
    ); 
    return 0; 
} 

Notez que ce n'est pas strictement équivalent au code Python dans votre question parce que les tuples ne doivent pas être sur des lignes distinctes. Par exemple, ceci:

1,2,3 4,5,6 

donnera la même sortie que:

1,2,3 
4,5,6 

Il est à vous de décider si c'est un bug ou une fonctionnalité :)

1

Son loin d'être aussi laconique , et bien sûr je n'ai pas compilé ça.

float atof_s(std::string & s) { return atoi(s.c_str()); } 
{ 
ifstream f("data.txt") 
string str; 
vector<vector<float>> data; 
while(getline(f, str)) { 
    vector<float> v; 
    boost::algorithm::split_iterator<string::iterator> e; 
    std::transform( 
    boost::algorithm::make_split_iterator(str, token_finder(is_any_of(","))), 
    e, v.begin(), atof_s); 
    v.resize(3); // only grab the first 3 
    data.push_back(v); 
} 
+0

Fugly, vous savez. Vous lisez CSV et vous le faites ressembler à une sorte de science de fusée. Rester simple. – JBeurer

17

Le C++ String Toolkit Library (StrTk) a la solution suivante à votre problème:

#include <string> 
#include <deque> 
#include "strtk.hpp" 

struct point { double x,y,z; } 

int main() 
{ 
    std::deque<point> points; 
    point p; 
    strtk::for_each_line("data.txt", 
         [&points,&p](const std::string& str) 
         { 
          strtk::parse(str,",",p.x,p.y,p.z); 
          points.push_back(p); 
         }); 
    return 0; 
} 

Plus exemples peuvent être trouvés Here

1

L'un des projets open-source de Sony Picture Imagework est Pystring, ce qui devrait permettre une traduction principalement directe des parties de séparation de ficelle:

Pystring est une collection de fonctions C++ qui correspondent à l'interface et au comportement des méthodes de classe de chaînes de Python en utilisant std :: string. Implémenté en C++, il ne nécessite ni n'utilise un interpréteur python. Il offre la commodité et la familiarité des opérations de chaîne commun non inclus dans la bibliothèque C++ standard

Il y a a few examples et some documentation

1

tous ces sont de bons exemples. mais ils ne répondez aux questions suivantes:

  1. un fichier CSV avec des numéros de colonne (quelques lignes avec plus de colonnes que d'autres)
  2. ou lorsque certaines valeurs ont un espace blanc (ya yb, x1 x2, x2 ,)

donc pour ceux qui cherchent encore, cette classe: http://www.codeguru.com/cpp/tic/tic0226.shtml est assez cool ... certains changements pourraient être nécessaires