Reading "complex" data from .txt file : revisited

Pages: 12
Hello, I am trying to read from a file made of groups of data separated by (xx xx xx)

Sample


// in TrainStations.txt
MavisBeacon (161 0 23 1)(162 3 109)(163 3 139)(164 3 99) (182 5 231 640)(68 3 121)...
JackyDons (255 4 229 4756)(217 3 150)(256 4 217 4957)(257 4 224 4931)(143 2 63 29)....
1
2
3
4
5
6
/*Where(using MavisBeacon (161 1 23 1) as an example) 
MavisBeacon is a station 
161 is a train number
1 is a type of train (enum coded as TrainType {seat, bed, open, closed, electric, gas};)
23 is number of seats and;
1 is presence of wifi*/

For an closed train(wagon), there are only three variables (id, type,volume) that is why some of the values are three in the brackets.

This is what i have done but i wonder if it works because my output seem to not get any values from the file

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
  // constants
const string STATIONS_DATA = "TrainStations.txt"; // file name that contains stations data
const char LEFT_PARAN = '(';
const char RIGHT_PARAN = ')';
const char SPACE = ' ';

// in trains.h
class Station
{
private:
	string sName;		// station name
	vector<unique_ptr<Vehicle>> vehicles;
	vector<string> stationNames;
///

// in trains.cpp
ifstream readData(STATIONS_DATA);
if (!readData.is_open())
	throw runtime_error("Error opening file containing train stations");
else
{
	string staName;					 // station name
	string id;						 // to read vehicle ID
	int type, para0, para1 = 0;		 // To read vehicle type and other parameters
	stationNames.clear();			// empty the contents of stations to avoid duplicate reads
	vehicles.clear();				// empty contents in vehicles to avoid duplicate reads
	//getline(readData, staName);		// read the station name from file
	while (getline(readData, staName))
	{
		while (readData.peek() == SPACE || readData.peek() == LEFT_PARAN)  // ignore  spaces and left parameter
		{
			readData.ignore();
		}

		readData >> id >> type >> para0;  // read parameters 

		if (readData.peek() != RIGHT_PARAN) 
		{
			readData >> para1;
		}

		readData.get();

		stationNames.push_back(staName);  // fill the stationName vector 
			
		string vehicleId=to_String(id);

		VehicleType t = static_cast<VehicleType>(type);

              // create appropriate Vehicle type and fill into vector
               switch (t)
			{
			case SEAT_WAGON:
				vehicles.push_back(unique_ptr<Vehicle>(new SeatVehicle(vehicleId, t, para0, para1)));
				break;
/// continue with the other stuff


I wonder why i don't get my stuff into vehicles (a vector)
Last edited on
your description is incomplete:

MavisBeacon (161 1 23 1) ...
JackyDons (255 4 229 4756) ...

for MavisBeacon:
161 - train#, 1 - TrainType(seat), 23 - #beds, 1 - bool hasWifi
for JackyDons:
255 - train#, 4 - TrainType (closed), 229 - #beds, 4756 - ??? (this can't be bool hasWifi)
Send all details accurately
@gunnerfunner
Sorry for not being more specific

enum TrainType{ SEAT(0), BED, OPEN, CLOSED, ELECTRIC, GAS(5)}

parameters for each type is as follows
1
2
3
4
5
6
7
8
9
10
11
SeatingWagon : id(string), type(enum), seats(int), internet(bool)

BedWagon:  id(string), type(enum), beds(int)

OpenWagon:  id(string), type(enum), capacity(int), area(int)

ClosedWagon:  id(string), type(enum), volume(int)

ElectricWagon:  id(string), type(enum), speed(int), power(int)

DieselWagon:  id(string), type(enum), speed(int), consumption(int)

Therefore

JackyDons (255 4 229 4756)...


means at the station JackyDons, there is an electric wagon{4} which has id 255 and has a max speed of 229 km/h with power 4756 KW

NB: Don't forget enums auto numbering starts at 0 so it is
enum TrainType{ SEAT(0), BED, OPEN, CLOSED, ELECTRIC, GAS(5)}

Hope i have given the needed details.
Last edited on
1
2
3
4
5
6
7
// in trains.h
class Station
{
private:
	string sName;		// station name
	vector<unique_ptr<Vehicle>> vehicles;
	vector<string> stationNames;


Why the vector<string> stationNames? I would think that each station has only one name. Perhaps you should have a vector<Station> outside the class to hold multiple stations?

Have you tried printing out the train data after you try to read it?

Why do each of your classes contain the "type" information? Don't the individual classes describe the "type"?
open train(wagon), there are only three variables (id number, type, area)
OpenWagon: id(string), type(enum), capacity(int), area(int)

notice the anomaly? rather than waste time going back and forth clarifying your mistakes post the full assignment
@jlb,
Why the vector<string> stationNames? I would think that each station has only one name.

Yes each station has just one name. I put that because in a some part of the assignment, one needs to print all the station names as a list. Maybe putting it outside the class may make more sense but i can't figure out why.

Why do each of your classes contain the "type" information? Don't the individual classes describe the "type"?

Those different Wagons are actually derived from the base class Wagon{};. A train can be attached with any of them and each attached must be identified by both its id(unique) and type.

@gunnerfunner,
open train(wagon), there are only three variables (id number, type, area)
OpenWagon: id(string), type(enum), capacity(int), area(int)
That was typo. I have corrected it. This project is giving me (unnecessary) stress.

notice the anomaly? rather than waste time going back and forth clarifying your mistakes post the full assignment


Yes, it is the anomaly that makes me post the question. Otherwise, if every type had the same number of variables then it would have been easier figure it out.

The problem description (in brief) is that
In a company, they have many trains and each train has an id. The train can be coupled with many different kinds of carriages(i call it wagon). There can be a wagon that transports goods and a wagon that transports persons.

Goods wagon can either be opened (no roof, e.g a wagon that carries timber) or closed (with roof like a container eg. a wagon that carries postal boxes) while the person wagons can either be one that has seats or one that has only beds (for overnight trains).

And the sample output i put there is from a txt file that contains all the trains stations, and their various wagons (that can be attached to a train).

This is a very complex project. All data is from txt files and no data is supposed to be entered by the user.

I don't know if i am clear enough.

My problem is extracting those different values given that some are (xx xx xx xx) and others (xx xx xx).

Last edited on
Use regular expressions to parse the lines?

Something along these lines, perhaps?
Note: Only two wagon types are shown for brevity,
Caveat: Compiled, but not even cursorily tested.

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
#include <string>
#include <memory>
#include <regex>
#include <vector>

enum TrainType{ SEAT = 0, BED = 1, OPEN= 2, CLOSED = 3, ELECTRIC = 4, GAS = 5 } ;

struct wagon
{
    std::string id ;

    explicit wagon( std::string id ) : id( std::move(id) ) {}

    // rule of five defaults
    virtual ~wagon() = default ;
    wagon( const wagon& ) = default ;
    wagon( wagon&& ) = default ;
    wagon& operator= ( const wagon& ) = default ;
    wagon& operator= ( wagon&& ) = default ;

    using pointer = std::unique_ptr<wagon> ;
};

struct seating_wagon : wagon
{
    seating_wagon() = default ;
    seating_wagon( std::string id, int seats, int inet )
        : wagon( std::move(id) ), seats(seats), inet(inet) {}

    int seats = 0 ;
    bool inet = false ;

    static pointer make( const std::string& str )
    {
        // expected: id(string), type(enum) == SEAT, seats(int), internet(bool)
        // eg. (xxx 0 45 1)
        static const std::regex re( R"(\((\w+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\))" ) ;
        std::smatch m ;
        if( std::regex_match( str, m, re ) && m[2] == SEAT )
        {
            try { return std::make_unique<seating_wagon>( m[1], std::stoi(m[3]), std::stoi(m[4]) ) ; }
            catch( const std::exception& ) {} // stoi failed, return nullptr
        }
        return {} ; // parse failed, return nullptr
    }
};

struct bed_wagon : wagon
{
    bed_wagon() = default ;
    bed_wagon( std::string id, int beds ) : wagon( std::move(id) ), beds(beds) {}

    int beds = 0 ;

    static pointer make( const std::string& str )
    {
        // expected: id(string), type(enum) == BED, beds(int)
        // eg. (1b 1 54)
        static const std::regex re( R"(\((\w+)\s+(\d+)\s+(\d+)\s+\))" ) ;
        std::smatch m ;
        if( std::regex_match( str, m, re ) && m[2] == BED )
        {
            try { return std::make_unique<bed_wagon>( m[1], std::stoi(m[3]) ) ; }
            catch( const std::exception& ) {} // stoi failed, return nullptr
        }
        return {} ; // parse failed, return nullptr
    }
};

struct station
{
    std::string name ;
    std::vector<wagon::pointer> wagons ;
};

bool parse( const std::string& line, station& s )
{
    static std::regex station_re( R"((\w+)\s+(\.+))" ) ;
    std::smatch m ;
    if( std::regex_match( line, m, station_re ) )
    {
        s = { m[1], {} } ;
        static const std::regex wagon_re( R"(\([^\)]+\))" ) ;
        const std::string str = m[2] ;
        std::sregex_iterator iter( str.begin(), str.end(), wagon_re ), end ;
        for( ; iter != end ; ++iter )
        {
            wagon::pointer p ;

            // try calling make one by one on each different type of wagon

            // cute: implement a chain of responsibility
            // https://sourcemaking.com/design_patterns/chain_of_responsibility

            // cuter: implement exemplars
            // Chapter 8, 'advance C++ Programming Styles and Idioms' by Coplien
            // http://www.powells.com/book/advanced-c-programming-styles-and-idioms-9780201548556
            if( ( p = seating_wagon::make(str) ) == nullptr ) p = bed_wagon::make(str) ;

            if(p) { s.wagons.push_back( std::move(p) ) ; return true ; }
        }
    }

    return false ; // parse error
}
Last edited on
Yes each station has just one name. I put that because in a some part of the assignment, one needs to print all the station names as a list. Maybe putting it outside the class may make more sense but i can't figure out why.

Why, because each station has only one name.

[quote]Why do each of your classes contain the "type" information? Don't the individual classes describe the "type"?


Those different Wagons are actually derived from the base class Wagon{};. A train can be attached with any of them and each attached must be identified by both its id(unique) and type. [/quote]

Since you seem to be trying to use inheritance, based on the vector<unique_ptr<Vehicle>> vehicles;, the derived class it's self should be the "type" it shouldn't need to have a type variable embedded within the class. Each time you "push" a "Vehicle" it would be any one of your different "Wagons".

Goods wagon can either be opened (no roof, e.g a wagon that carries timber) or closed (with roof like a container eg. a wagon that carries postal boxes) while the person wagons can either be one that has seats or one that has only beds (for overnight trains).

Which would be the reason for the derived classes. Each of the different classes would handle a different "type" of Vehicle.

To read this file I recommend you use a few stringstreams to parse the file. First read one entire line into a string, then parse that string starting with the station name. Then read and discard the '(' delimiter. Now read your Id, by the way why is the Id a string your file seems to only contain numbers for the Id? Once you've read the Id (every "Vehicle" has an Id), then read the type. Once you have the type you can figure out (possibly by using a switch statement) how many variables follow and the purpose of those variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    // Read one entire line at one time.
    while(getline(file_in, line))
    {
        Station current_station;

        // Use a stringstream to help parse the line.
        std::stringstream sin(line);
        char delimiter;
        // Read the station name and read and throw away the first '('.
        sin >> current_station.sName >> delimiter;
        // Now read each "Vechile".
        int train_number;
        int type;
        sin >> train_number;
        sin >> type;

        switch(type)
        {
            case SEAT:
            {


But to me it really looks like you need to step back and make sure you have your inheritance modeled correctly.

How many "Station"s must your program accommodate? If multiples then perhaps a vector<Station> is needed outside any class. Your Station would have a single name, and possibly multiple "Train"s.

How many "Train"s can each "Station" accommodate at one time? If multiple trains then perhaps the "Station" requires a vector<Train>. A single "Train" would consist of multiple "Vehicle"s.

How many "Vehicle"s can each "Train" contain. Again if multiple "Vehicle"s then perhaps you need a vector<Vehicle> in your Train class.

How many different "Vehicle"s are possible. Remember you need a class, derived from "Vehicle" for each of the different "Vehicle"s.
YES! Now i feel as if the problem has been understood by @JLBorges and @jlb.

@JLBorges, your solution looks straight but i am afraid we might not be on the same plane. First, i haven't wetted my hands with regex(only tried once with some simple bash script). Second, (or maybe because of the first reason), i find it difficult understanding your solution. Maybe i need to study one more course in C++ to get there.

@jlb you have understood the problem very well.
... it really looks like you need to step back and make sure you have your inheritance modeled correctly.
TRUE i have to take a second look at my inheritance. Your questions
How many...
are all relevant to the problem.

To read this file I recommend you use a few stringstreams to parse the file...
should be the ideal. I don't know why i didn't get that earlier. Thanks for the eye opener.

Right now, implementing suggestions.....
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# include <iostream>
# include <algorithm>
# include <string>
# include <vector>
# include <fstream>
# include <sstream>
# include <utility>

enum TrainType {seat, bed, open, closed, electric, diesel};

std::ostream& operator << (std::ostream& os, const TrainType& t)
{
    switch(t)
    {
        case(seat):  os << "seat "; break;
        case (bed):  os << "bed "; break;
        case (open):  os << "open "; break;
        case (closed):  os << "close "; break;
        case (electric):  os << "electric "; break;
        case (diesel):  os << "diesel "; break;
    }
    return os;
}

struct Train
{
    std::string m_id;
    TrainType m_type;
    int m_seats = 0;
    bool m_hasInternet = false;
    int m_beds = 0;
    int m_capacity = 0;
    int m_area = 0;
    int m_volume = 0;
    int m_speed = 0;
    int m_power = 0;
    int m_consumption = 0;

    Train(const std::string& id, const TrainType& type, const int seats, bool hasInternet)
    : m_id(id), m_type(type), m_seats(seats), m_hasInternet(hasInternet){}//seatingWagon

    Train(const std::string& id, const TrainType& type, const int bedORvolume)
    : m_id(id), m_type(type)
    {
        switch(type)
        {
            case bed: m_beds = bedORvolume; break;//bedWagon
            case closed: m_volume = bedORvolume; break;//closedWagon
        }
    }
    Train (const std::string& id, const TrainType& type, const int capacityORspeed, const int areaORconsumption)
    : m_id(id), m_type(type)
    {
        switch(type)
        {
            case open: m_capacity = capacityORspeed; m_area = areaORconsumption; break; //openWagon
            case electric: m_speed = capacityORspeed; m_power = areaORconsumption; break; //electricWagon
            case diesel: m_speed = capacityORspeed; m_consumption = areaORconsumption; break; //dieselWagon
        }
    }
};

std::ostream& operator << (std::ostream& os, const Train& t)
{
    os << t.m_id << " " << t.m_type << " ";
    switch(t.m_type)
    {
        case seat:
            os << t.m_seats << " " << t.m_hasInternet; break;
        case bed:
            os << t.m_beds; break;
        case open:
            os << t.m_capacity << " " << t.m_area; break;
        case closed:
            os << t.m_volume; break;
        case electric:
            os << t.m_speed << " " << t.m_power; break;
        case diesel:
            os << t.m_speed << " " << t.m_consumption; break;
    }
    os << "\n";
    return os;
}

struct Station
{
    std::string m_name;
    std::vector<Train> m_trains;
    Station (const std::string& name, const std::vector<Train>& trains)
    : m_name(name), m_trains(trains){}
};
std::ostream& operator << (std::ostream& os, const Station& s)
{
    os << s.m_name << ": ";
    for (const auto& elem : s.m_trains) os << elem;
    return os;
}

int main()
{
    std::ifstream inFile{"C:\\test.txt"};
    std::vector<Station> stations{};

    if(inFile)
    {
        std::string line{};
        while (getline(inFile, line))
        {
            std::string stationName = line.substr(0, line.find('('));
            auto itrTrail = line.begin();
            auto itrAdv = line.begin();
            std::vector<std::string> trainDetails{};
            while (itrAdv != --line.end())
            {
                auto itrOpen = std::find(itrTrail, line.end(), '(');
                auto itrClose = std::find(itrAdv, line.end(), ')');

                if((itrOpen != line.end()) && (itrClose != line.end()))
                {
                    trainDetails.emplace_back(std::string{itrOpen+1, itrClose});
                }
                if(itrClose == --line.end())
                {
                    break;
                }
                else
                {
                    itrTrail = itrClose + 1;
                    itrAdv = itrTrail + 1;
                }
            }
            for (const auto& elem : trainDetails)
            {
               // std::cout << elem << "\n";
                std::string trainName = elem.substr(0, elem.find(' '));
                while (trainName.back() == ' ')trainName.pop_back();
                std::string otherTrainDetails = elem.substr(elem.find(' ')+1);
                std::vector<Train> trains{};
                std::istringstream stream{otherTrainDetails};
                int temp{};
                std::vector<int> otherTrainDetailsInt{};
                while (stream >> temp)
                {
                    if(stream)
                    {
                        otherTrainDetailsInt.push_back(temp);
                    }
                }
              //  for (const auto& elem : otherTrainDetailsInt)std::cout << elem << " "; std::cout << "\n";
                switch(otherTrainDetailsInt[0])
                {
                    case 0:
                    {
                       trains.emplace_back(Train(trainName, static_cast<TrainType>(otherTrainDetailsInt[0]),
                        otherTrainDetailsInt[1], static_cast<bool>(otherTrainDetailsInt[2])));
                        break;
                    }
                    case 1:
                    {
                       trains.emplace_back(Train(trainName, static_cast<TrainType>(otherTrainDetailsInt[0]),
                        otherTrainDetailsInt[1]));
                        break;
                    }
                    case 2:
                    {
                       trains.emplace_back(Train(trainName, static_cast<TrainType>(otherTrainDetailsInt[0]),
                        otherTrainDetailsInt[1], otherTrainDetailsInt[2]) );
                        break;
                    }
                    case 3:
                    {
                        trains.emplace_back(Train(trainName, static_cast<TrainType>(otherTrainDetailsInt[0]),
                        otherTrainDetailsInt[1]));
                        break;
                    }
                    case 4:
                    {
                        trains.emplace_back(Train(trainName, static_cast<TrainType>(otherTrainDetailsInt[0]),
                        otherTrainDetailsInt[1], otherTrainDetailsInt[2]));
                        break;
                    }
                    case 5:
                    {
                       {
                            trains.emplace_back(Train(trainName, static_cast<TrainType>(otherTrainDetailsInt[0]),
                            otherTrainDetailsInt[1], otherTrainDetailsInt[2]));
                            break;
                        }
                    }

                }
                if(inFile)stations.emplace_back(stationName, trains);

            }
        }
    }
    std::ofstream outFile{"C:\\output.txt"};
    for (const auto& elem : stations)
    {
        std::cout << elem;
        outFile << elem;
    }
}

tested with the following file:
MavisBeacon (161 1 23 1)(162 3 109)(163 3 139)(164 3 99) (182 5 231 640)(68 3 121)
JackyDons (255 4 229 4756)(217 3 150)(256 4 217 4957)(257 4 224 4931)(143 2 63 29)
OP: there are still inconsistencies b/w your data and what you say about it - for e.g. in this post

http://www.cplusplus.com/forum/beginner/215505/#msg1001239

you say bed is the second enum element (i.e value 1) with 3 arguments in its ctor while the first train at MavisBeacon as described in the original post is also enum 1 with 4 arguments in its ctor. so you'll have to clear such mismatches and then the above program should tie up any loose ends automatically
Last edited on
@gunnerfunner, thanks for the elaborate solution. I am back to making the classes work together.

@jlb, this is how i intend to build the program.
Since a train has many wagons of different types,(i call it Vehicle in my program). I start by
1. make wagons(Vehicle as i call it) class(different types with different specs)
2. Train class(has many wagons)
3. Station( has many trains)

This is how my inheritance is designed (Vehicles class)

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
enum VehicleType{SEAT_WAGON, BED_WAGON, OPEN_WAGON, CLOSED_WAGON, ELECTRIC_ENGINE, DIESEL_ENGINE};

class Vehicle
{
protected:
	string wagId;				// wagon id
	VehicleType wagType;		// wagon type

public:
	// default constructor
	Vehicle(string i, VehicleType t):wagId(i), wagType(t){}

// destructor : virtual because it is a base class
	virtual ~Vehicle(){}

/// all other member functions needed for the other classes, either set to empty or return zero eg
       virtual int getSeats() const { return 0; }
	virtual bool hasInternet() const { return false; }

/// and then seatVehicle
//SeatVehicle class
class SeatVehicle : public Vehicle
{
private:
	int seats;				// number of seats present
	bool internet;			// if there is internet or not

public:
	// default constructors
	SeatVehicle(string wId, VehicleType sw = SEAT_WAGON, int nSeats = 0, bool connected = false)
		:Vehicle(wId, sw), seats(nSeats), internet(connected) {}

	virtual ~SeatVehicle() {}
        virtual int getSeats() const { return seats; }
	virtual bool hasInternet() const { return internet; }

/// and others

class BedVehicle : public Vehicle
{
private:
	int beds;	// number of beds present
public:
	BedVehicle(string gwId, VehicleType bw = BED_WAGON, int bed = 0)
		: Vehicle(gwId, bw), beds(bed) {}

/// and the rest functions specific for BedVehicle 


Hope i am not lost.

Maybe this threat topic needs to be changed.

Concerning the file reading, i think the input from @gunnerfunner and @jlb are enough to get the stuff working.
Last edited on
Ok, after some work, i finally have something that reads

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
ifstream readStation(STATIONS_DATA);
	if (!readStation.is_open())
		throw runtime_error("Error opening file containing train stations");
	else
	{
		int intType = 0;
		VehicleType type;				 // vehicle type
		string staName;					 // station name
		int intId;						 // to read vehicle ID
		int para0, para1 = 0;		    // To read other parameters

		stationNames.clear();			// empty the contents of stations to avoid duplicate reads
		vehicles.clear();				// empty contents in vehicles to avoid duplicate reads

		vector<string> lines;
		string line;				 // line to read each station
		while (getline(readStation, line))
		{
			lines.push_back(line);
			istringstream iss(line);
			char delim;
			iss >> staName >> delim;			  // read the station name and the '('
			stationNames.push_back(staName);    // fill stationNames vector

			iss >> intId >> intType;		// read the wagon id and the type
			
			string id = toString(intId);
			type = static_cast<VehicleType>(intType);

			//print(staName), space(), print(id), space(), print(type), space();
			
			switch (type) // check type. Type determines how many parameters are left to be read
			{
			case SEAT_WAGON:
				iss >> para0 >> para1;
				vehicles.push_back(unique_ptr<Vehicle>(new SeatVehicle(id, type, para0, para1)));
				break;
			case BED_WAGON:
				iss >> para0;
				vehicles.push_back(unique_ptr<Vehicle>(new BedVehicle(id, type, para0)));
				break;
			case OPEN_WAGON:
				iss >> para0 >> para1;
				vehicles.push_back(unique_ptr<Vehicle>(new OpenVehicle(id, type, para0, para1)));
				break;
			case CLOSED_WAGON:
				readStation >> para0;
				vehicles.push_back(unique_ptr<Vehicle>(new ClosedVehicle(id, type, para0)));
				break;
			case ELECTRIC_ENGINE:
				iss >> para0 >> para1;
				vehicles.push_back(unique_ptr<Vehicle>(new ElectricEng(id, type, para0, para1)));
				break;
			case DIESEL_ENGINE:
				iss >> para0 >> para1;
				vehicles.push_back(unique_ptr<Vehicle>(new DieselEng(id, type, para0, para1)));
				break;
			}
		}
// print stuff in the vehicles vector
		VehicleInfo info;
		for (auto &e : vehicles)
		{
			e->getVehicleInfo(info);
			tab(), showVehicleInfo(info);
		}
		
	}


This however, reads just the first set of values after the station name
@gunnerfunner, yes, i fixed the anomaly. I am very SORRY for that.

// testing with 
MavisBeacon (161 0 23 1)(162 3 109)(163 3 139)(164 3 99) (182 5 231 640)(68 3 121)...
JackyDons (255 4 229 4756)(217 3 150)(256 4 217 4957)(257 4 224 4931)(143 2 63 29)...
St. Jones (273 0 84 0)(238 3 108)(239 3 93)(240 3 107)(241 3 141)(242 3 113)(243 3 141)...
// yields (i have another function that prints vehicle information ==> void getVehicleInfo()

Vehicle id        : 161
Type of Vehicle   : Seat carriage
Number of seats   : 23
Wagon has internet: Yes


Vehicle id        : 255
Type of engine    : Electric engine
Maximum speed     : 229 km/h
Engin power       : 4756 kW


Vehicle id        : 273
Type of Vehicle   : Seat carriage
Number of seats   : 84
Wagon has internet: No



How do i iterate through the different values per line such that i can read all values (xx xx xx xx)?

@gunnerfunner's work seem to go through the whole line.

My thought: find first '(' the the other ')' , group everything between these as one string then manipulate. Is that feasible?

size_t pos1 = str.find("(");

size_t pos2 = str.find(")");

then string newLine = str.substr(pos1, pos2);


Thanks in advance for your thoughts.
Last edited on
How do i iterate through the different values per line such that i can read all values (xx xx xx xx)?

@gunnerfunner's work seem to go through the whole line.

I have a feeling something might be getting lost in translation here ... you want to read all values, OK. gunnerfunner's work reads whole line, OK. so, what seems to be the problem then?
Well I still wonder why you need a "type" variable in your class. It seems to me that you only need the "type" to determine what derived class to create, but once you create the correct derived class that class should know it's properties.

Next why does the base class have functions like getSeat(), hasInternet()? Your base class should only have functions and variables that are common to all derived classes. Does a Open and Closed wagon have seats or even care about internet?

You may want to review some documentation on polymorphism for more information.
http://www.cplusplus.com/doc/tutorial/polymorphism/

Also I don't recommend using default arguments with constructors. Instead create individual constructors with fewer parameters and remember that with C++11 or greater you can call other constructors from constructors.

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 <string>

enum VehicleType{SEAT_WAGON, BED_WAGON, OPEN_WAGON, CLOSED_WAGON, ELECTRIC_ENGINE, DIESEL_ENGINE};

class Vehicle
{
    protected:
        std::string wagId;
    public:
        Vehicle(const std::string& iD): wagId(iD) {}

        // destructor : virtual because it is a base class
        virtual ~Vehicle() {}
        std::string getId() {return wagId;}
        // Pure virtual function, all descendants must override this function. 
        virtual VehicleType getType() = 0;

};

class SeatVehicle : public Vehicle
{
    private:
        int seats;              // number of seats present
        bool internet;          // if there is internet or not

    public:
        // constructors
        SeatVehicle() = default;
        SeatVehicle(const std::string& wId) : Vehicle(wId), seats(0), internet(false) {}
        SeatVehicle(const std::string& wId, bool hasInternet) : Vehicle(wId), seats(0), internet(hasInternet) {}
        SeatVehicle(std::string wId, int nSeats, bool connected)
            : Vehicle(wId), seats(nSeats), internet(connected) {}

        void setValues(const std::string& wId, int numberSeats, bool hasInternet)
        {
            wagId = wId;
            seats = numberSeats;
            internet = hasInternet;
        }

        VehicleType getType()
        { // SeatVechile will always be a type SEAT_WAGON.
            return SEAT_WAGON;
        }


};

#include <iostream>

int main()
{
    Vehicle *seatWagon = new SeatVehicle{"myId", 32, true};
    std::string myId = seatWagon->getId();

    std::cout << myId << std::endl;

    std::cout << seatWagon->getType() << std::endl;

    delete seatWagon;
}




Thanks @JLBorges for your solution. It has inspired me to read more about regex and i can not use it to some extend. And i had never thought of
rule of five defaults
. I often use rule of three without knowing that it was a rule http://en.cppreference.com/w/cpp/language/rule_of_three

@gunnerfunner, i have incorporated your solution and now my program works like magic. Thanks for taking a closer look at the problem.

@jlb, Thanks for pointing this out
Your base class should only have functions and variables that are common to all derived classes
You say..
I don't recommend using default arguments with constructors
Why? Bad practice?

Thanks for the framework for the classes. I was making lots of unnecessary lines. About my default constructor: i did it so because that is how the objects are saved in file. Needless making others that i will not eventually use. I am away of the fact that i could make others.

Again thank you three for your inputs. They are very inspiring. I must say that your solutions and opinions in this platform are very helpful to me even when i just read from others' problems. Keep the good work.
Last edited on
Consider this:

Ideally, each wagon class should be responsible for interpreting its own data (in this case from the string containing the information). For instance, seating_wagon and seating_wagon alone should have knowledge about the format of the information for seating_wagon. That way, the knowledge is encapsulated, and the code becomes far more maintainable - for instance, if we later want to add another member variable or change the type of an existing member in seating_wagon, only that class would need to be modified.

Try to avoid god-like components which know everything about everything. A grand switch-case construct based on the type of each derived class is almost always a tell-tale sign of poor design.
About my default constructor: i did it so because that is how the objects are saved in file.

Do you realize that the "default constructor" is a constructor with no arguments that the compiler supplies until you create another constructor? Once you create another constructor this "default constructor" is no longer created.

You say..
"I don't recommend using default arguments with constructors"
Why? Bad practice?

IMO, yes.

Needless making others that i will not eventually use. I am away of the fact that i could make others.

I was showing the other constructors in order to get rid of the default arguments. And by having different constructors you would be able to have multiple ways of constructing your classes. For example many times it is useful to create an "empty" class which could be accomplished by using the no argument constructor, or you might want to create a derived class where you provide the "base" information and perhaps a string containing the information for the "derived" portion of the class.
OP: if you have some time and interest you'd be well served taking a look at Andrei Alexandrescu's 'Modern C++ Design', chapter on 'Object Factories' that describes how to create instances of polymorphic types in run-time in a 'proper' fashion
@jlb by default constructor i intended to mean default arguments it is a mistake(wrong word).
For example many times it is useful to create an "empty" class which could be accomplished by using the no argument constructor..
very useful. I use that most of the times to "test" that my member functions work properly.

@JLBorges
...if we later want to add another member variable or change the type of an existing member in seating_wagon, only that class would need to be modified...
== TRUE. I have modified my work.

Thanks @gunnerfunner for the tip. I was a click away from the book when title was googled. I now have it and i will go through all bits of the book to keep myself abreast with C++.

Well guys, bear with me that all this whole stuff about inheritance/polymorphism just came into my my "library" last month and as a distant student, i have to grasp everything and now a massive project has to incorporate everything. Difficult but i am moving there and with your help, i am confident i will reach there. From your comments i have learned a lot (even more than 1 week of my study time).

Can anyone of you opt to be a mentor? I just need someone to discuss and build my thoughts into code and eventually get criticism(s) after my try. PM if you can. I think 1hr 1-on-1 per week will do.

Thanks in advance
Last edited on
Pages: 12