C++ Input data from file, specific format

So this is my first time posting a question here so please bear with me. I am studying computer science for my bachelors, and I would like some help. We have created various Classes that are all part of a Roster Management System. I have all the classes set up and such, and I can figure out how to store everything in a dynamically allocated array later on, for the time being I am having a difficult time just reading the data needed from a file.

The format for the text file is as follows:

1
2
3
4
5
6
7
course1-name | course1-code | number-credits | professor-name
student1 first name | student1 last name|credits|gpa|mm/dd/yyyy|mm/dd/yyyy
student2 first name | student2 last name|credits|gpa|mm/dd/yyyy|mm/dd/yyyy
end_roster|
course2-name | course2-code | number-credits | professor-name
student1 first name | student1 last name|credits|gpa|mm/dd/yyyy|mm/dd/yyyy
end_roster|


As you can see, the rosters only have four data fields, course name, course code, number of credits and the professors name.

Students have 6 fields, 7 if you include the end_roster marker (the end_roster marker is literally just that, not a bool value or anything.

Anyway, I cant seem to figure out how to read this input properly. I can read it all in and tokenize it, but then I don't know how to append each "section" to its correct class. This happens to give me each "token" (not 100% clear what that is, but it seems to append each set of characters into a string). I do not know how to assign it into their proper places from this point forward though.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ifstream myroster("rosters.txt");
while( ! myroster.eof() )
{
    getline(myroster, line);
    cout << line << endl << endl;
    char c[line.length() + 1];
    strcpy (c,  line.c_str());
    char * p = strtok(c, "|");
    while (p != 0)
    {
        cout << p << endl;
        p = strtok(NULL, "|");
        counter++;
    }
}
myroster.close();



I've also tried the following;

1
2
3
4
5
6
ifstream myroster("rosters.txt");
while (!myroster.eof())
{
    getline(myroster, line, '|');
    counter++;
}


Both methods had some form of counter implementation (my first attempt). For example; if counter == 4 it'll be appended to roster, or counter == 6 and its students, but nothing worked (I realized the flaw later on)

The reason I don't just hardcode the program to implement line one for roster1 information, line 2 for student 1, line 3 for student 2, and etc is because the data needs to be edited in the program, and one of the options is to add students/remove them and add rosters/remove them, after which the file needs to be updated again, and then read in again after.

Can anyone shed some light on this?
Last edited on
Something along these lines:

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
#include <iostream>
#include <vector>
#include <string>
#include <sstream>

struct student
{
    std::string first_name ;
    std::string last_name ;
    std::string credits ;
    std::string gpa ;
    std::string first_date ;
    std::string second_date ;
};

constexpr char delimiter = '|' ;

bool to_student( const std::string& line, student& s )
{
    std::istringstream stm(line) ;

    if( std::getline( stm, s.first_name, delimiter ) &&
        std::getline( stm, s.last_name, delimiter ) &&
        std::getline( stm, s.credits, delimiter ) &&
        std::getline( stm, s.gpa, delimiter ) &&
        std::getline( stm, s.first_date, delimiter ) &&
        std::getline( stm, s.second_date ) ) return true ;

    s = student() ;
    return false ;
}

struct course
{
    std::string course_name ;
    std::string course_code ;
    std::string number_of_credits ;
    std::string professor_name ;
    std::vector<student> students ;
};

bool to_course( const std::string& line, course& c )
{
    std::istringstream stm(line) ;

    if( std::getline( stm, c.course_name, delimiter ) &&
        std::getline( stm, c.course_code, delimiter ) &&
        std::getline( stm, c.number_of_credits, delimiter ) &&
        std::getline( stm, c.professor_name, delimiter ) ) return true ;

    c = course() ;
    return false ;
}

int main()
{
    std::istringstream text_file
    (
        "algorithms|CS104|5|DE Knuth\n"
        "Andrei|Tarkovsky|5|25|01/12/2013|31/03/2014\n"
        "Yasujiro|Ozu|4.5|22.5|01/12/2013|31/03/2014\n"
        "end_roster|\n"
        "metaprogramming|CS201|4|Dave Abrahams\n"
        "Ingmar|Bergman|4.7|18.8|01/12/2013|31/03/2014\n"
        "Bela|Tarr|4.5|18|01/12/2013|31/03/2014\n"
        "Krzysztof|Kieslowski|4.6|18.4|01/12/2013|31/03/2014\n"
        "end_roster|\n"
    );

    std::vector<course> roster ;

    std::string line ;
    while( std::getline( text_file, line ) )
    {
        course c ;
        if( to_course( line, c ) )
        {
            student s ;
            while( std::getline( text_file, line ) && to_student( line, s ) )
                c.students.push_back(s) ;
        }
        roster.push_back(c) ;
    }

    for( const course& c : roster )
    {
        std::cout << c.course_code << ' ' << c.course_name << ": "
                   << c.professor_name << '\n' ;

        for( const student& s : c.students )
            std::cout << "    " << s.first_name << ' ' << s.last_name << '\n' ;

        std::cout << '\n' ;
    }
}

http://coliru.stacked-crooked.com/a/35fca3e0d7ef0465
i GREATLY appreciate your answer! It makes some sense, but unfortunately we are not allowed to use vectors :(

However, I may be able to adapt some of your code for my use if that is okay with you. Would you mind explaining what this does?

1
2
3
4
5
6
7
8
9
10
11
12
bool to_course( const std::string& line, course& c )
{
    std::istringstream stm(line) ;

    if( std::getline( stm, c.course_name, delimiter ) &&
        std::getline( stm, c.course_code, delimiter ) &&
        std::getline( stm, c.number_of_credits, delimiter ) &&
        std::getline( stm, c.professor_name, delimiter ) ) return true ;

    c = course() ;
    return false ;
}


> However, I may be able to adapt some of your code for my use if that is okay with you.

Yes. That is what it was written for; for you to understand and adapt it to your specific requirements.
For instance, you may need to add more robust error handling.


> explaining what this does?

to_student() tries to initialize a student object from a string (expected to be a line containing student information read from the file); returns true on success.
to_course() does likewise for a course.

String streams: http://latedev.wordpress.com/2011/11/16/c-stringstreams/
std::getline() with a delimiter: http://www.cplusplus.com/reference/string/string/getline/
Hmm, after completely failing with ALL my ideas thus far, I attempted to use your code and adapt it. Unfortunately, I am having a bit of difficulty. I can somewhat understand the "to_course" and "to_student," however, given that I have defined constructors it doesn't work exactly like that. Also, my definition for Class and Students is different, as noted below:

Student.h
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
#include <iostream>
#include <string>

#include "Date.h"

using namespace std;

// Structure definition
struct Students
{
	// Private Values
private:
    string fname;
    string lname;
    string standing;
    int credits;
    double gpa;
    Date dob;
    Date mdate;
	
	// Public values
public:
	// Default constructor
    Students();
	
	// Constructor with inline variables
    Students(string f, string l, int c, double g, Date d, Date m);
    
	// Mutators
    void setFirstName(string f);
    void setLastName(string l);
    void setStanding();
    void setCredits(int c);
    void setGPA(double d);
    void setDOB(Date d);
    void setMDate(Date m);
    
	// Accessors
    string getFirstName() const;
    string getLastName() const;
    string getStanding() const;
    int getCredits() const;
    double getGPA() const;
    Date getDOB() const;
    Date getMDate() const;
	
	// Operator Overloading
    bool operator==(const Students& y) const;
    bool operator!=(const Students& y) const;
    bool operator<(const Students& y) const;
    bool operator>(const Students& y) const;
    bool operator<=(const Students& y) const;
    bool operator>=(const Students& y) const;
	
    friend ostream& operator<< (ostream&, const Students&);
    friend istream& operator>> (istream&, Students&);

	// Function to convert to uppercase
    void touppercase();
    
	// Output Function
    void output() const;
	// Input Function
    void input();
};


Class.h
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
#include <iostream>
#include <string>

#include "Students.h"

using namespace std;

struct Class
{
	
private:
    string course_name;
    int course_code;
    int credits;
    string instructor;
    int counter;
    int size;
    Students** list;
		
	// Students roster[MAX];
	
	// Grow Function
    void grow();
    
public:
	// Default Constructors
    Class();
    Class(string cn, int cc, int c, string i, int s, int count);
	
	// Destructor
    ~Class();
	
	// Copy Constructor
    Class(const Class& other);
	
	// Mutators
    void setCoursename (string cn);
    void setCoursecode (int cc);
    void setCredits (int c);
    void setInstructor (string i);
	
	// Accessors
    string getCoursename();
    int getCoursecode();
    int getCredits();
    string getInstructor();
    int getCounter();
	
	// Functions
    void addStudent();
    void removeStudent();
    void findStudent();
	
	// Print functions
    void printAll();
    void printAllStudents();
    void printClass();
	
	// Input function
    void input();
	
    friend ostream& operator<< (ostream&, const Class&);
    friend istream& operator>> (istream&, Class&);
	
    Class& operator= (const Class& rightside);
	
	// Sort function
	//	void sort(Students** a, int size);
    void sort(Students**, int);	
};


As you can see, my classes use int, double, Date objects, and string. Is it possible to modify your code to make up for this?
Last edited on
> given that I have defined constructors it doesn't work exactly like that.

No. Instead, you would have to read the values into local variables and then construct your objects from them.


> my classes use int, double, Date objects, and string.
> Is it possible to modify your code to make up for this?

Yes.
To read a string delimied by '|':
1
2
3
4
5
std::string s ;
if( std::getline( stm, s, '|' ) )
{
     // read it correctly
}

To read an int delimied by '|':
1
2
3
4
5
6
int i ;
char delim ;
if( stm >> i >> delim && delim == '|' )
{
     // read it correctly
}


Perhaps, making the classes streamable would be a good idea. Something like this:

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <iostream>
#include <string>
#include <sstream>
#include <vector>

constexpr char delimiter = '|' ;
const std::string end_marker = "end_roster|" ;

struct A
{
    explicit A( const std::string s = "", int i = 0, double d = 0 )
           :  str(s), n(i), r(d) {}

    std::string name() const { return str ; }
    int number() const { return n ; }
    double value() const { return r ; }

    private:
        std::string str ;
        int n ;
        double r ;
};

std::ostream& operator<< ( std::ostream& stm, const A& a )
{
    return stm << a.name() << delimiter << a.number() << delimiter
                << a.value() << '\n' ;
}

std::istream& operator>> ( std::istream& stm, A& a )
{
    std::string line ;
    if( std::getline(stm,line) )
    {
        std::istringstream strstm(line) ;
        char delim ;
        std::string s ;
        int n ;
        double d ;
        if( std::getline( strstm, s, delimiter ) &&
            strstm >> n >> delim && delim == delimiter &&
            strstm >> d )
        {
            a = A(s,n,d) ;
            return stm ;
        }
    }

    a = A() ; // clear the data
    stm.clear( stm.rdstate() | std::ios::failbit ) ; // put the stream in a failed state
    return stm ;
}

struct B
{
    explicit B( int i = 0, const std::string& s = "" ) : n(i), str(s) {}

    void add( const A& a ) { seq.push_back(a) ; }

    int id() const { return n ; }
    std::string name() const { return str ; }
    const std::vector<A>& objects() const { return seq ; }

    private:
        int n ;
        std::string str ;
        std::vector<A> seq ;
};

std::ostream& operator<< ( std::ostream& stm, const B& b )
{
    stm << b.id() << delimiter << b.name() << '\n' ;
    for( const A& a : b.objects() ) stm << a ;
    return stm << end_marker << '\n' ;
}

std::istream& operator>> ( std::istream& stm, B& b )
{
    std::string line ;
    if( std::getline(stm,line) )
    {
        std::istringstream istm(line) ;
        char delim ;
        int n ;
        std::string s ;
        if( istm >> n >> delim && delim == delimiter &&
            std::getline( istm, s ) )
        {
            b = B(n,s) ;
            std::string line_a ;
            while( std::getline(stm,line_a) )
            {
                std::istringstream strstm(line_a) ;
                A a ;
                if( strstm >> a ) b.add(a) ;
                else break ;
            }
            if( line_a == end_marker ) return stm ;
        }
    }
    b = B() ; // clear the data
    stm.clear( stm.rdstate() | std::ios::failbit ) ; // put the stream in a failed state
    return stm ;
}


int main()
{
     B b1( 100, "one" ) ;
     b1.add( A( "two", 200, 3.45 ) ) ;
     b1.add( A( "three", 300, 4.56 ) ) ;
     b1.add( A( "four", 400, 5.67 ) ) ;

     B b2( 500, "five" ) ;
     b2.add( A( "six", 600, 7.89 ) ) ;
     b2.add( A( "seven", 700, 8.91 ) ) ;
     b2.add( A( "eight", 800, 9.12 ) ) ;
     b2.add( A( "nine", 900, 1.23 ) ) ;

     std::string text ;
     {
         std::ostringstream stm ;
         stm << b1 << b2 ;
         text = stm.str() ;
         std::cout << text << "\n----------\n\n" ;
     }

     std::vector<B> seq ;
     std::istringstream stm(text) ;
     B b ;
     while( stm >> b ) seq.push_back(b) ;

     for( const B& b1 : seq ) std::cout << b1 ;
}

http://coliru.stacked-crooked.com/a/09fcee0e0d1f51fa
@JLBorges - Looking at the code I see you use an explicit consturtor, is it nessarry and where in the code is it protecting you? Or are you putting there as a matter of practice?
> Or are you putting there as a matter of practice?

As a matter of habit: drop the explicit if and only if there is a specific design intent to facilitate implicit conversions via the constructor.
Topic archived. No new replies allowed.