Разбор строк в C ++ (стиль Python) - PullRequest
19 голосов
/ 11 февраля 2009

Мне нравится, как в Python я могу сделать что-то вроде:

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

В основном это чтение списка линий, каждая из которых представляет точку в трехмерном пространстве, точка представлена ​​в виде трех чисел, разделенных запятыми

Как это можно сделать в C ++ без особой головной боли?

Производительность не очень важна, этот анализ выполняется только один раз, поэтому простота важнее.

P.S. Я знаю, что это звучит как вопрос новичка, но поверьте мне, я написал лексер на D (почти как C ++), который включает в себя чтение некоторого текстового символа char и распознавание токенов,
просто, возвращаясь к C ++ после долгого периода python, я просто не хочу тратить свое время на такие вещи.

Ответы [ 10 ]

24 голосов
/ 11 февраля 2009

Я бы сделал что-то вроде этого:

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 должны быть числами с плавающей точкой.

И включают в себя:

#include <iostream>
#include <fstream>
17 голосов
/ 20 апреля 2009

Библиотека C ++ String Toolkit (StrTk) имеет следующее решение вашей проблемы:

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

Больше примеров можно найти Здесь

16 голосов
/ 11 февраля 2009

За исключением этих хороших примеров, в C ++ вы обычно переопределяете operator >> для вашего типа точек, чтобы достичь чего-то вроде этого:

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

или даже:

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

Соответствующая реализация оператора может очень походить на код j_random_hacker.

14 голосов
/ 11 февраля 2009
#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;
}
7 голосов
/ 11 февраля 2009

Этот ответ основан на предыдущем ответе j_random_hacker и использует 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;
}
4 голосов
/ 11 февраля 2009

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

Обратите внимание, что это не совсем эквивалентно коду Python в вашем вопросе, потому что кортежи не должны быть в отдельных строках. Например, это:

1,2,3 4,5,6

даст тот же результат, что и:

1,2,3
4,5,6

Вам решать, является ли это ошибкой или функцией:)

3 голосов
/ 11 февраля 2009

Вы можете читать файл из std :: iostream построчно, помещать каждую строку в std :: string и затем использовать boost :: tokenizer, чтобы разбить его. Это будет не так элегантно / коротко, как у Python, но намного проще, чем читать вещи в персонаже одновременно ...

1 голос
/ 19 апреля 2011

все это хорошие примеры. пока они не отвечают на следующее:

  1. файл CSV с разными номерами столбцов (в некоторых строках больше столбцов, чем в других)
  2. или когда некоторые значения имеют пробел (ya yb, x1 x2,, x2,)

так что для тех, кто все еще ищет, этот класс: http://www.codeguru.com/cpp/tic/tic0226.shtml довольно круто ... могут потребоваться некоторые изменения

1 голос
/ 25 октября 2009

Один из проектов Sony Picture Imagework с открытым исходным кодом - Pystring , который должен обеспечивать в основном прямой перевод частей, разделяющих строки:

Pystring - это коллекция функций C ++, которые соответствуют интерфейсу и поведению методов строкового класса Python с использованием std :: string. Реализованный в C ++, он не требует и не использует интерпретатор python. Он обеспечивает удобство и знакомство с общими строковыми операциями, не включенными в стандартную библиотеку C ++

Есть несколько примеров и немного документации

1 голос
/ 11 февраля 2009

Это далеко не так кратко, и, конечно, я не скомпилировал это.

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