read text file and store them into arrays/vector

Hi , I am trying to read a text file and store the contents into members of a class called "profile" and display them. The text file contains the followings:
Could you please help me code so that they are stored properly?
John
34
New York
0 image.hdr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
class profile{
private:
    string Name;
    int Age;
    string Place;
    string<vector>  Data;
public:
    void get(string name,int age, string place, string<vector> data)
    {
        Name= name;
        Age= age;
        Place= place;
        Data= data;
    }
int main()
{
string Name;
string Age;
string place;
string<vector> data;

ifstream myfile;
myfile.open("data.txt");

}
  


Thank you for your help!
Hello ETlinny,

My first question is regarding line 4 of the input file. Will this line contain two words or could it contain more? And do you need the vector in the class or just an extra variable?

Line 12 confuses me. You call the function "get" yet it looks like either a "set" function or more of an overloaded ctor.

Line 10 and 24 the definition of vector is wrong. See: http://www.cplusplus.com/reference/vector/vector/ and http://www.cplusplus.com/reference/vector/vector/vector/ for an example of how to define a vector.

You have opened your file, but need to check that the stream is open and good. A simple way is:
1
2
3
4
if (!myfile)
    //print error message. Your choice of message.
    std::this_thread::sleep_for(std::chrono::seconds(3));  // <--- Needs header files chrono" and "thread".
    exit(1);  // <--- 1 meaning there is a problem. And exit because there is no reason to continue. 


Actually I would create a function for the read and open and close the file stream there.

I would more like create a vector of classes where each element of the vector is an individual class with different information.

So far you have not created an object of the class "profile" to use. Although if you create a vector of classes you will not have to define the object in main. This will be done in the read function where I would create a "temp" object to use when reading the file and add that to the vector when finished.

Before I forget you will need to include the header file "<vector>" before yo can use a vector.

on line 22 in main you define age as a string, but everywhere else you define age as an int. These should match otherwise you are creating more work than you need.

I need to work on this to see what I can come up with.

Hope that helps,

Andy
Hello Andy,

Thank you for you reply. I am a complete beginner and not exactly sure what I am doing.
My aim is to store each lines from the input file into a vector of classes where each element of the vector is an individual class with different information as you have mentioned.

For the last line of the input file, it actually contains two variables: an ID number and an image file corresponding to the ID number .Both are to be stored separately with variable name "ID" and"filename" .This is so that I can load the image file(s) later. The input file may have one or more IDs and images.





I've done a bunch of these and have similar code floating around, so could help. The key here is the spaces -- what are the possibilities for spacing in the input file? Also you can use the [ output ] ... [ /output ] tags (no spaces near brackets) for the file examples.
Your initial example has a single first name, which likely could be multiple words (John Smith), and the location could probably also be single or multi-word. The last line would be cleaner if the image path was on its own line, especially if the image path could possibly contain spaces?

Is this a valid file?
John Benjamin Smith
34
Los Angeles, California
13815 my awesome image.hdr




Hello ETlinny,

I do have the program working at least to the read part. As a beginner whit I did is likely ahead of what you know, but because of what you want to do you may have to learn earlier.

What I did was to create a function, "ReadFile()", to read the file. To do this I created a vector of type Profile in main and passed this vector by reference to the function.

As a side note when it comes to functions, classes and structs I like to capitalize the first letter of the name. This helps me to remember that it is not a variable name.

Inside the function I defined the variables needed to use when reading the file. The last is a vector to deal with line four of the input file. I created two other "std:string"s to use later.

After opening the file stream I check to make sure it did open properly. See code from last message.

I used a while loop to read the file: while (std::getline(myfile, Name)). This will read the file until there is nothing left. When the "myfile" stream fails so also will the while loop.

Inside the while loop I read the other lines of the file. I used this to read the "Age":
1
2
myfile >> Age;
myfile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.  

Line 2 will clear the new line (\n) from the input buffer which is needed before the next "std::getline" makes a read. Note: "myfile" can be replaced with "std::cin" when working with the key board.

I used "std::getline" to read the name and place because these fields could have two or more words and since these variables are "std::strings" this is the best way. When reading "Age", defined as an int, I used the formatted input. Hense the above code.

After reading the last line of the record into a "std::string" I called "line" I put this into a string stream where I used a while loop to extract each part of the string stream, generally referred to and used as "ss" although you could use any name, this makes reading the stream as being just a small portion of the entire file. This when you have read everything from the "ss" it is like reaching the "eof". I did this in order to fill a vector before creating object of the class to put in the vector of classes.

The last thing to do is put the information into the vector that was created in main and passed to the function.

I know that you may not understand some or most of this, so create what code you can so I can see what you can do and we can work it out from there.

Hope that helps,

Andy
@Handy Andy, the problem with your suggested while loop condition there is every line gets written to the variable Name. I also stumbled on this while testing a few ideas.
Age, indeed, is different from the rest, in that preferably the parsing is done with '>>' , straight into a formatted int. Of course, one must now manually swallow the newline following it, just like you have.

@ETlinny, with the assumption of data path possibly having spaces, the data line is also unique. First, it'll be simpler if you design Profile's data parts to be flat (data_id, data_path) rather than nested (vector<Data>), especially since there are only two parts. Second, you'll likely need a regex to carefully split up the line.

main() is getting pretty big, so likely you'll want to move all file parsing logic to its own method, taking along a reference to your profiles vector.

Example running at https://repl.it/repls/ImperturbableQualifiedVendor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <iostream>
#include <limits>
#include <regex>
using namespace std;

struct Profile
{
    Profile()
    {
    }

    Profile(string full_name, int age, string location, string data_id,  string data_path) :
        full_name(full_name),
        age(age),
        location(location),
        data_id(data_id),
        data_path(data_path)
    {
    }
    
    string full_name;
    int age;
    string location;
    string data_id;
    string data_path;
};

// Parses an input stream into a Profile and appends to profiles
void ParseStream(istringstream& iss, vector<Profile>& profiles)
{
    string full_name;
    int age;
    string age_string;
    string location;
    string data_id;
    string data_path;
    
    // These three vars will be used to split up the last line of file
    string data_line;
    regex data_r( R"((\d+)\s+([\w \.]+))" );
    smatch data_matches;

    // First full line + automatic newline swallowing.
    getline(iss, full_name);
    
    // Extracts age as formatted integer and manually swallows newline.
    iss >> age;
    iss.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); 
    
    // Location
    getline(iss, location);
    
    // Data
    getline(iss, data_line);
    if (regex_search(data_line, data_matches, data_r))
    {
        data_id = data_matches[1];
        data_path = data_matches[2];
    }
    else
    {
        data_id = -1;
        data_path = "Unknown Path";
    }
    
    profiles.emplace_back(full_name, age, location, data_id, data_path);
}

int main()
{
    const char* profile_text = 
R"LITERAL(John Benjamin Smith
34
Los Angeles, California
13815 my awesome image.hdr
)LITERAL";

    // Imitate file stream for online demo purposes.  To use actual file,
    //   you'd do something like
    // string file_name("my_file.txt");
    // ifstream ifs(file_name);
    // if (!ifs)
    // {
    //     cout << "Could not open file \"" << file_name << "\". Please check path!";
    //     return -1;
    // }
    // Once you're finished with file, you can call an ifs.close();
    istringstream iss(profile_text);
    vector<Profile> profiles;
    ParseStream(iss, profiles);

    // Show profiles
    cout << "Profiles found:" << endl;
    for (auto& p : profiles)
    {
        cout << "Name: \"" << p.full_name << "\"" << endl << 
                "Age: " << p.age << endl <<
                "Location: \"" << p.location << "\"" << endl <<
                "Data id: " << p.data_id << endl <<
                "Data path: \"" << p.data_path << "\"" << endl;
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
using namespace std;

class profile
{
private:
   string Name;
   int Age;
   string Place;
   int ID;
   string Filename;
public:
   void get(string name,int age, string place, int id, string filename )
   {
       Name= name;
       Age= age;
       Place= place;
       ID = id;
       Filename = filename;
   }

   friend istream &operator >> ( istream &strm, profile &P );
   friend ostream &operator << ( ostream &strm, const profile &P );
};


istream &operator >> ( istream &strm, profile &P )
{
   while( getline( strm, P.Name ) && P.Name == "" );
   strm >> P.Age >> ws;
   getline( strm, P.Place );
   strm >> P.ID >> P.Filename >> ws;
   return strm;
}


ostream &operator << ( ostream &strm, const profile &P )
{
   strm << "Name:     " << P.Name     << '\n';
   strm << "Age:      " << P.Age      << '\n';
   strm << "Place:    " << P.Place    << '\n';
   strm << "ID:       " << P.ID       << '\n';
   strm << "Filename: " << P.Filename << '\n';
   return strm;
}


int main()
{
   vector<profile> people;
   profile P;
   ifstream myfile( "data.txt" );
   while ( myfile >> P ) people.push_back( P );
   myfile.close();

   for ( profile P : people ) cout << P << '\n';
}


data.txt
John Fitzgerald Kennedy
34
New York
0 image.hdr

George Washington
50
Ohio
1 image2.hdr


Output
Name:     John Fitzgerald Kennedy
Age:      34
Place:    New York
ID:       0
Filename: image.hdr

Name:     George Washington
Age:      50
Place:    Ohio
ID:       1
Filename: image2.hdr


Hello @last chance, thank you very much for your detailed code, as I am a beginner, I have very limited knowledge of c++.However , I am pretty comfortable of class members and operation functions and your code help me understand the topic better.


Another question is that there can be more than one ID number and one image file from the input text file. In such case can I use a for loop and store them as a string as well?
E.g
data.txt

John Fitzgerald Kennedy
34
New York
0 image.hdr
1 image2.hdr
2 image3.hdr
3 image4.hdr
4 image5.hdr
.
.
.
.
30 image30.hdr


Output

Name:     John Fitzgerald Kennedy
Age:      34
Place:    New York
ID:       0
Filename: image.hdr

Name:     George Washington
Age:      50
Place:    Ohio
ID:       0 1 2 3 4 5 6 7 ...30
Filename: image2.hdr  image2.hdr  image3.hdr  image4.hdr ...



I'm sorry if my question seem a bit hard to understand.

@icy1, thank you for your help and detailed comments. However, code block doesn't seem to recognise #include <regex> library.
Last edited on
Another question is that there can be more than one ID number and one image file from the input text file. In such case can I use a for loop and store them as a string as well?


Yes, but you must decide on the format of the input file. In particular, you must decide how to indicate where one record finishes and the next one begins. Otherwise, the code will not know when to stop reading in IDs/filenames and when to restart reading a name. You could do that in lots of ways, including
- a blank line
- a sentinel character of your choice
- blind hope that a name never starts with a number ("1st Baron Rothermere" etc. )

So, you need to decide a fixed input format first.
ETlinny wrote:
However, code block doesn't seem to recognise #include <regex> library.

Introduced as part of C++11 standard, which is an excellent minimum version to use. You'll need at least Visual Studio 2012, (e.g. the free Visual Studio Community edition for educational or open-source purposes), or at least GCC 4.8.1 on linux.
Topic archived. No new replies allowed.