Reading Data from Text File. I need to do two things, first I need to read the number of points

StackOverflow https://stackoverflow.com/questions/16799973

  •  30-05-2022
  •  | 
  •  

Question

I have a text file arranged as:

Diam
<D>          4.2      
05:21:26 02:Apr:2012
Point 1
<X>       2   
<Y>       5
<Z>       6  
Point 2
<X>       4   
<Y>       2
<Z>       0
Point 3
<X>       4 
<Y>       1 
<Z>       2 
End
End

I need to do two things, first I need to read the number of points, so basically read how many lines start with Point, for this I wrote:

#include <iostream>
#include <string>

using namespace std;

int main()
{
    //count number of data points in file (only count lines starting with P)
    const char FileName[] = "myfile.txt";
    int num = 0.;
    string line;
    ifstream file;
    file.open (FileName);
    while(file.eof() == false)
    {
        getline(file, line);
        if (line[0]=='P')  num++;
    }
    file.close();

    return 0;
}

This gives me the correct number of points (although improvements in the code are welcome), and secondly I need to read the x, y, and z coordinates and disregard everything else. Not sure how to go about this, any suggestions?

Thanks in advance!

EDIT:

Thanks everybody for great ideas and answers! Captain Obvlious provided a great answer, and I want so see if it can be adapted a bit.

So, turns out the .txt file will have the format:

Diam
<D>          4.2      5    6    4   2
05:21:26 02:Apr:2012
Point 1
<X>       2   5    6    4   2
<Y>       5   4    4    8   3
<Z>       6   7    6    0   2
Point 2
<X>       4   2    6    4   2
<Y>       2   3    5    8   4
<Z>       0   7    6    3   2
Point 3
<X>       4   0    6    2   2
<Y>       1   5    6    7   4
<Z>       2   0    6    5   3
End
End

Again, I only need the first column of values, everything else after that can be discarded. I used int for simplicity but the numbers will be double. After the reading I am trying to place the values in an armadillo matrix, so my matrix looks like:

cout << matrix << endl;
2 5 6
4 2 0
4 1 2 

So, I modified Captain Obvlious' solution to place the data in the matrix:

#include <iostream>
#include <fstream>
#include <string>
#include <cctype>
#include <sstream>
#include <stdexcept>
#include <armadillo>

using namespace std;
using namespace arma;

int main()
{
    mat matrix(3,3);  //here is where I set the size of the matrix, 
                      //different files have different number of points,
                      //that's why I use my counting points code to set the size.
    const char FileName[] = "myfile.txt";
    string line;
    ifstream file;
    file.open (FileName);

    for(;;)
    {
        getline(file, line);
        if( file.eof() ) {
            break;
        }

        if (line.size() && line[0]=='P')
        {
            // Change "int" to "double" if necessary.
            struct { double  x, y, z; } data;

            for(int i = 0; i < 3; i++)
            {
                getline(file, line);
                if(line.size() > 3 && line[0] == '<' && line[2] == '>')
                {
                    string::value_type pointElement = line[1];

                    // skip spaces and process data here to get the value
                    string::size_type offset = line.find(' ');
                    if(string::npos == offset)
                    {
                        throw invalid_argument("Invalid data format");
                    }

                    stringstream sline(line.substr(offset));
                    int value;
                    if(!(sline >> value))
                    {
                        throw invalid_argument("invalid data format");
                    }


                    switch(pointElement)
                    {
                    case 'X':   data.x = value; break;
                    case 'Y':   data.y = value; break;
                    case 'Z':   data.z = value; break;
                    default:
                        // error in data format
                        throw invalid_argument("invalid data format");
                    }
                }
                else
                {
                    // error in data format
                    throw invalid_argument("invalid data format");
                }
            }

            // Do something with the values in data
            cout
                << "point[x=" << data.x
                << ", y=" << data.y
                << ", z=" << data.z
                << "]" << endl;
            // place data in matrix
            // need to loop over k where k is the row of the matrix
            matrix(k,0) = data.x; // format is matrix(rows,columns)
            matrix(k,1) = data.y;
            matrix(k,2) = data.z;
        }
    }
    file.close();

    return 0;
}

Not sure where to place the loop over k that sets the row of the matrix to allocate the values. Sorry for the super long post, and thank you in advance for any help. This is pretty useful stuff to have around for future reference in my opinion.

Was it helpful?

Solution

You need to process each of the point's data lines individually and store them into some type of data structure so that they can be processed after X, Y and Z have been loaded. You should also include some additional validation code otherwise you are likely to introduce undefined behavior. There are several ways to do this, some will cause you to duplicate code others concentrate on assigning the data after it has been processed.

The solution below takes into account both validation and the fact that the data must be read from 3 individual lines. It's not perfect and expects the "myfile.txt" file to be in an exact format. It uses exceptions to handle errors in the data format and adds a data structure for saving the data while it's being loaded.

#include <iostream>
#include <fstream>
#include <string>
#include <cctype>
#include <sstream>
#include <stdexcept>

using namespace std;

int main()
{
    const char FileName[] = "myfile.txt";
    string line;
    ifstream file;
    file.open (FileName);

    for(;;)
    {
        getline(file, line);
        if( file.eof() ) {
            break;
        } 

        if (line.size() && line[0]=='P')
        {
            // Change "int" to "double" if necessary.
            struct { int x, y, z; } data;

            for(int i = 0; i < 3; i++)
            {
                getline(file, line);
                if(line.size() > 3 && line[0] == '<' && line[2] == '>')
                {
                    string::value_type pointElement = line[1];

                    // skip spaces and process data here to get the value
                    string::size_type offset = line.find(' ');
                    if(string::npos == offset)
                    {
                        throw invalid_argument("Invalid data format");
                    }

                    stringstream sline(line.substr(offset));
                    int value;
                    if(!(sline >> value))
                    {
                        throw invalid_argument("invalid data format");
                    }


                    switch(pointElement)
                    {
                    case 'X':   data.x = value; break;
                    case 'Y':   data.y = value; break;
                    case 'Z':   data.z = value; break;
                    default:
                        // error in data format
                        throw invalid_argument("invalid data format");
                    }
                }
                else
                {
                    // error in data format
                    throw invalid_argument("invalid data format");
                }
            }

            // Do something with the values in data
            cout
                << "point[x=" << data.x
                << ", y=" << data.y
                << ", z=" << data.z
                << "]" << endl;
        }
    }
    file.close();

    return 0;
}

When run against "myfile.txt" it produces the following output

point[x=2, y=5, z=6]
point[x=4, y=2, z=0]
point[x=4, y=1, z=2]

OTHER TIPS

Maybe you want to consider using ifstream/stringstreams to make your life easier, though this would not be the fastest way...

int pt[3];
string line,dummy;

... //to where you were
  if (line[0]=='P')
    {
        num++;
        for (int i =0; i<3; ++i)
        {
            getline(file,line);
            stringstream ss(line);
            ss>>dummy;
            ss>>pt[i];
            std::cout<<"got "<<pt[i]<<std::endl;
        }
    }

you should also include sstream and fstream

inside your while loop, you could always add something like this:

if(line.size() > 3){
  if(line[0] == '<' && line[1] == 'X' && line[2] == '>'){
    //enter code here
  } else if(line[0] == '<' && line[1] == 'Y' && line[2] == '>'){
    //enter code here
  } else if(line[0] == '<' && line[1] == 'Z' && line[2] == '>'){
    //enter code here
  }
}

then do something like substring the line and remove < X > / < Y > / < Z > and any spaces or special characters.

after that you can cast the remaining string to whatever you need

edit: a slightly better way

  if(line.find("< X> ") != string::npos){
     //enter code here
  } else if(line.find("< Y> ") != string::npos){
     //enter code here
  } else if(line.find("< Z> ") != string::npos){
     //enter code here
  } 

This is quite similar S Grimminck's answer.

#include <iostream>
#include <string>
using namespace std;
int main()
{
const char FileName[] = "myfile.txt";

int num = 0;
int x = 0;
int y = 0;
int z = 0;
string line;
std::stringstream str_parse(line);
ifstream file;
file.open (FileName);

while(file.eof() == false)
{
    getline(file, line);

    if (line[0]=='P')  
        num++;
    if( line.size() > 3 )
    {
    if( (line[0] == '<') && (line[1] == 'X') && (line[2] == '>'))
    {
       line = line.substr(3,line.size());
       str_parse>>x; // Gets the x-coordinate
    } 
    else if((line[0] == '<') && (line[1] == 'Y') && (line[2] == '>'))
    {
       line = line.substr(3,line.size());
       str_parse>>y;  // Gets the y-coordinate
    } 
    else if((line[0] == '<') && (line[1] == 'Z') && (line[2] == '>'))
    {
       line = line.substr(3,line.size());
       str_parse>>z;  // Gets the z-coordinate
    }
    }
}
file.close();

return 0;
}    

This is the first time I'm using stringstream. So, I might be wrong. Feel free to correct me. :)

Do you really need to count the number of points in advance? Why not just do one pass over the input file?

#include <iostream>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

struct Point {
    Point() {}
    Point(int x_, int y_, int z_) : x(x_), y(y_), z(z_) {}

    int x, y, z;
};

istream& operator>>(istream& is, Point& p) {
    is >> ws;

    if (is.peek() != 'P') {
        is.setstate(ios::failbit);
        return is;
    }

    string pointStr;
    is >> pointStr;
    if (pointStr != "Point") {
        is.setstate(ios::failbit);
        return is;
    }

    int pointIdx;
    is >> pointIdx;

    string coordStr;
    int x, y, z;

    is >> coordStr;
    if (coordStr != "<X>") {
        is.setstate(ios::failbit);
        return is;
    }
    is >> x;

    is >> coordStr;
    if (coordStr != "<Y>") {
        is.setstate(ios::failbit);
        return is;
    }
    is >> y;

    is >> coordStr;
    if (coordStr != "<Z>") {
        is.setstate(ios::failbit);
        return is;
    }
    is >> z;

    p = Point(x, y, z);

    return is;
}

int main() {
    ifstream fileStream("data.txt");

    string diamStr;
    fileStream >> diamStr;
    if (diamStr != "Diam")
        throw istream::failure("Badly formatted file");

    string dStr;
    fileStream >> dStr;
    if (dStr != "<D>")
        throw istream::failure("Badly formatted file");
    double diam;
    fileStream >> diam;

    string time, date;
    fileStream >> time >> date;

    istream_iterator<Point> pointIt(fileStream);
    vector<Point> points;
    copy(pointIt, istream_iterator<Point>(), back_inserter(points));

    cout << points.size() << " points:\n";
    for_each(begin(points), end(points), [](const Point& p) {
        cout << "(" << p.x << ", " << p.y << ", " << p.z << ")\n";
    });
}

You could create a coordinates array like this: int coords[num*3];

Then, store the coordinate numbers in their respective position using modulus function and finding the position with pos mod 3 = 0 for x-coords, pos mod 3 = 1 for y-coords and pos mod 3 = 2 for z-coords.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top