Extract information from a colon delimited file - C++ - c++

I am trying to extract information into class objects from a colon delimited file. Each line of the file is set up in the same format. Here are the first few lines of the file:
s:Charles:Babbage:80:530213286:1133764834:mechanical engineering:3.8
e:Marissa:Meyer:37:549114177:53321:ceo:4456000
s:Alonzo:Church:92:586312110:1100539644:mathematics:4.0
e:Dana:Ulery:74:573811211:23451:engineer:124569
This is a school project and the purpose is to teach us about class inheritance. We have a base class Person and two child classes Student and Employee. We are supposed to import and store the information for students into Student objects and employee into Employee objects. I have an array of objects for each class; I'm sorting the students into the array of Student objects, same for employees, and in addition adding all people to the array of People objects.
I don't know what to do to get each piece of information with the delimiting commas. Right now I'm trying to use .getline but it doesn't seem to be working. How do I use this function (or another function) to extract information between delimiters into char arrays? Here's what I have so far for the case that the data is for an employee:
ifstream fin;
char* tempImport;
tempImport = new char[50];
int* tempIntArray;
tempIntArray = new int[10];
double tempDouble;
int tempInt;
// get the specifier of student or employee
fin.getline(tempImport, ':');
if(tempImport[0]=='e'){
// get first name
fin.getline(tempImport, ':');
employees[employeeIndex].setFirstName(tempImport);
allPeople[personIndex].setFirstName(tempImport);
// get last name
fin.getline(tempImport, ':');
employees[employeeIndex].setFirstName(tempImport);
allPeople[personIndex].setFirstName(tempImport);
// get age
fin.getline(tempImport, ':');
employees[employeeIndex].setAge(tempImport[0] - 0);
allPeople[personIndex].setAge(tempImport[0] - 0);
// get SSN
fin.getline(tempImport, ':');
for(int i=0;i<9;i++){
tempIntArray[i] = tempImport[i] - 0;
}
employees[employeeIndex].setSsn(tempIntArray);
allPeople[personIndex].setSsn(tempIntArray);
// get Employee ID
fin.getline(tempImport, ':');
for(int i=0;i<5;i++){
tempIntArray[i] = tempImport[i] - 0;
}
employees[employeeIndex].setEmpID(tempIntArray);
// get title
fin.getline(tempImport, ':');
employees[employeeIndex].setTitle(tempImport);
// get salary
fin >> tempDouble;
employees[employeeIndex].setSalary(tempInt);
employeeIndex++;
personIndex++;
}

It looks like you're missing a parameter when you call ifstream::getline(). See:
http://www.cplusplus.com/reference/istream/istream/getline/
You need the 3-parameter version of the method in order to specify a delimeter. When you call the 2-parameter version it interprets ':' as the streamsize. (Basically, ':' just resolves to the ASCII code for a colon, so that number gets passed in. What you really want for streamsize is the length of your tempImport buffer.)
However, if I may suggest (and your assignment allows it), the std::getline() version of the function may be better. (It allows you to use std::string instead of char*, which is a more C++ish way of doing things. Also you don't have to worry about if your input is bigger than your buffer.) Here's the documentation on that:
http://www.cplusplus.com/reference/string/string/getline/
So basically you could do something like this:
std::string tempImport;
std::getline(fin, tempImport, ':');
As a debugging suggestion, you could print tempImport after each time you call getline() on it (regardless of which kind you use). Take those out before you submit, but those print statements could help you debug your parsing in the meantime.
std::stderr << "getline(): " << tempImport << std::endl;
Edit:
Regarding the comment below, I was able to get this to compile. (It doesn't do anything useful, but shows that std::getline() is indeed present and compiles.) Does it compile for you?
#include <fstream>
int main (int argc, char** argv)
{
std::ifstream ifs;
std::string str;
std::getline(ifs, str, ':');
return 0;
}

If you'll pardon my saying so, you seem to have been taught one of the worst possible imitations of 'object oriented programming' (though if it's any comfort, it's also a fairly common one).
Personally, I think I'd write things quite a bit differently.
I'd probably start by eliminating all the setSalary, setTitle, etc. They're a horrible perversion of what OOP was supposed to do, losing a great deal in readability while gaining nothing in encapsulation.
Rather than providing member functions to manipulate all the members of the class, the class should provide a higher-level member to reconstitute an instance of itself from a stream.
When you do get the data, you probably do not want to create separate objects for your arrays of People/Employees/Students. Rather, each item will go into the array of either Employees or Students. Then the People will be just an array of pointers to the items in the other two arrays.
As to the details of reading the data: personally I'd probably write a ctype class that classified : as whitespace, and then just read data. For your class, you probably want to stick to using getline though.
class Person {
virtual std::istream &read(std::istream &is);
friend std::istream &operator>>(std::istream &is, Person &p) {
return p.read(is);
}
};
class Student : public Person {
std::string first_name;
std::string last_name;
std::string age;
std::string ssn;
std::string ID;
std::string title;
std::string salary;
virtual std::istream &read(std::istream &is) {
std::getline(is, first_name, ':');
std::getline(is, last_name, ':');
std::getline(is, age, ':');
// ...
return is;
}
};
With those in place, reading data from the file will normally be pretty simple:
std::string t;
Employee e;
Student s;
while (std::getline(infile, t, ':'))
if (t == "e") {
infile >> e;
Employees.push_back(e);
}
else if (t =="s") {
infile >> s;
Students.push_back(s);
}

Related

How to serialize and deserialize an object into/from binary files manually?

I've been trying to write the below object into a file and got lot of trouble since strings are dynamically allocated.
class Student{
string name, email, telephoneNo;
int addmissionNo;
vector<string> issued_books;
public:
// There are some methods to initialize name, email, etc...
};
So I got to know that I can't just write into a file or read from a file an object with serialization. So I searched all over the internet about serialization with cpp and got to know about Boost library.But I wanted to do it my own (I know writing a library that already exist is not good, but I wanna see what's going on inside the code). So I got to know about overloading iostream << and >>. And I also know that serialize/deserialize into/from text.
But I want to serialize into a binary file. So I tried overloading ostream write and istream read. But then I got size issues(as write and read needs the sizeof the object it writes/reads).Then I also got to know about stringstream can help to serialize/deserialize objects into/from binary. But I don't how to do that?
So my real question is How to serialize and deserialize an object into/from binary files without third party libraries?
I have found a solution serialize and deserialize an object into/from a file. Here is an explaination
As I told you this is my class. And I have added two functions which overload the iostream's write and read.
class Student{
string name, email, telephoneNo;
int addmissionNo;
vector<string> issuedBooks;
public:
void create(); // initialize the private members
void show(); // showing details
// and some other functions as well...
// here I'm overloading the iostream's write and read
friend ostream& write(ostream& out, Student& obj);
friend istream& read(istream& in, Student& obj);
};
But I have also told you that I have tried this already. The problem I have was how to read without object member's size. So I made changes as below (Please read comments also).
// write: overload the standard library write function and return an ostream
// #param out: an ostream
// #param obj: a Student object
ostream& write(ostream& out, Student& obj){
// writing the objet's members one by one.
out.write(obj.name.c_str(), obj.name.length() + 1); // +1 for the terminating '\0'
out.write(obj.email.c_str(), obj.email.length() + 1);
out.write(obj.telephoneNo.c_str(), obj.telephoneNo.length() + 1);
out.write((char*)&obj.addmissionNo, sizeof(obj.addmissionNo)); // int are just cast into a char* and write into the object's member
// writing the vector of issued books
for (string& book: obj.issuedBooks){
out.write(book.c_str(), book.length() + 1);
}
return out;
}
// read: overload the standard library read function and return an istream
// #param in: an istream
// #param obj: a Student object
istream& read(istream& in, Student& obj){
// getline is used rather than read
// since getline reads a whole line and can be give a delim character
getline(in, obj.name, '\0'); // delimiting character is '\0'
getline(in, obj.email, '\0');
getline(in, obj.telephoneNo, '\0');
in.read((char*)&obj.addmissionNo, sizeof(int));
for (string& book: obj.issuedBooks){
getline(in, book, '\0');
}
return in;
}
As you can see I have wrote length+1 for the terminating '\0'. It is usefull in read function as we have used getline instead of read. So getline reads until the '\0'. So no need of a size. And here I'm writing and reading into/from a file.
void writeStudent(Student s, ofstream& f){
char ch; // flag for the loop
do{
s.create(); // making a student
f.open("students", ios::app | ios::binary); // the file to be written
write(f, s); // the overloaded function
f.close();
cout << "Do you want to add another record? (y/n): ";
cin >> ch;
cin.ignore();
} while(toupper(ch) == 'Y'); // loop until user stop adding records.
}
void readStudent(Student s, ifstream& f){
char ch; // flag for the loop
do{
f.open("students", ios::in | ios::binary);
cout << "Enter the account no of the student: ";
int no;
cin >> no;
int found = 0;
while (read(f, s)){
if (s.retAddmissionNo() == no){
found = 1;
s.show();
}
}
if (!found)
cout << "Account Not found!\n";
f.close();
cout << "Do you want another record? (y/n): ";
cin >> ch;
} while(toupper(ch) == 'Y');
}
That's how I solved my problem. If something wrong here please comment. Thank you!

Reading a word from a file overloading operator >>

I want to overload the operator >>, so I can read some data from a file to save it in my class. My problem is that I don't know how to read one single word. Does a function like get() or getline() exist for this purpose?
For example, I have this class:
class Person{
private:
char * name;
int id;
public:
....
I have this file with some info:
James 23994
Anne 23030
Mary 300392
And what I want is to read the name and the id of these people to save them in my class.
Things are easier if you use std::string instead of a bare pointer that requires you to allocate memory, keep track of the size (or rely on null-termination), etc...
struct Person {
std::string name;
int id;
};
Now you can use the already existing operator<< for std::string and int :
std::istream& operator>>(std::istream& in, Person& p) {
in >> p.name >> p.id;
return in;
}
Note that operator>> does read input until it finds a ' ' by default, hence to "read a word" you dont have to do anything extra.
If you insist on having private fields, you should declare the operator as friend of the class.

Getting errors trying to pass a vector to a class's function C++

Im trying to read a text file "dictionary.txt" that contains some words with their definition and type. Each word is meant to be loaded into a Word class object with the definition and type, this object is then meant to be pushed to a vector array of other Word objects.
However I'm getting the errors:
E0147 declaration is incompatible with "void Dictionary::loadDictionary(std::vector<<error-type> std::allocator<<error-type>>> &vect)" (declared at line 27)
and
E0020 identifier "loadDictionary" is undefined.
I'm pretty new to C++ and OOP in general so would love some help with these errors.
Thanks heaps!
Code:
#include "stdafx.h"
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Dictionary
{
public:
void loadDictionary(vector<Word>& vect);
private:
Word w1;
string word;
string def;
string type;
};
void Dictionary::loadDictionary(vector<Word>& vect)
{
ifstream dicFile;
dicFile.open("dictionary.txt");
if (!dicFile)
{
cout << "File not found!" << endl;
exit(1);
}
int count1 = 0;
while (!dicFile.eof())
{
w1 = new Word;
dicFile >> word;
dicFile >> def;
dicFile >> type;
w1.word->word;
w1.def->def;
w1.type->type;
vect.push_back(w1);
}
}
class Word
{
public:
private:
string word;
string definition;
string type;
};
Word::Word() {
word = "";
definition = "";
type = "";
}
int main()
{
Dictionary d;
vector<Word> word;
d.loadDictionary(word);
return 0;
}
Here is a set of suggestions to make everything work and start thinking
to the problem in a more OOP way (but this may be subjective).
As others pointed out, the main problem is that w1 is a Word and you try to do
w1 = new Word;
This makes no sense, since new Word creates a pointer to a Word (a Word*),
which is not what you want. C++ is not Java, in which everything is an implicit
pointer to something. Here you can have automatic objects (Word) and pointers
to objects (Word*).
From a class design point of view you create a Word which is supposed to keep together the three
strings word, definition, and type. Ok. What is a Dictionary? The name suggests
it is a container for words, so the vector should be an attribute of the Dictionary,
rather than a parameter which gets filled. Otherwise the name should have been
DictionaryLoader or something in those lines.
So I'd start by fixing the Word class. To make things simpler I suggest that you have
everything public, so I'll use struct instad of class. Following
Google C++ Style Guide I
added an underscore after the member variable names. Since the initialization is not needed,
I'd avoid it. Instead you will load words from a stream, so it may be a nice idea
to have a method for loading a word. An operator would be even better, but let's
leave it for the future.
The way you were reading didn't allow for definitions including spaces. So I took the liberty of using getline to use quoted strings (no quotes inside!).
This is an example dictionary.txt (which you should have included in your question! Remember Minimal, Complete, and Verifiable example):
sovereign "a king or queen" noun
desk "a type of table that you can work at, often one with drawers" noun
build "to make something by putting bricks or other materials together" verb
nice "pleasant, enjoyable, or satisfactory" adjective
And here goes the code.
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
struct Word {
std::string word_;
std::string definition_;
std::string type_;
std::istream& read(std::istream& is) {
is >> word_;
std::string skip;
std::getline(is, skip, '"');
std::getline(is, definition_, '"');
is >> type_;
return is;
}
};
Now the Dictionary. A dictionary is a container for words, so our dictionary should
have a vector of words inside it. All the variables which were in your dictionary
were not really in the right place. You were using them as temporaries, so they should
have been placed inside your function.
struct Dictionary {
std::vector<Word> vect_;
bool load(const std::string& filename) {
std::ifstream is("dictionary.txt");
if (!is)
return false;
while (true) {
// Read
Word w;
w.read(is);
// Check
if (!is)
break;
// Use
vect_.push_back(w);
}
/* Alternative
Word w;
while (w.read(is)) { // Read & Check
// Use
vect_.push_back(w);
}*/
/* Another alternative
for (Word w; w.read(is);) { // Read & Check
// Use
vect_.push_back(w);
}*/
return true;
}
};
int main()
{
Dictionary d;
if (d.load("dictionary.txt"))
return EXIT_SUCCESS;
else
return EXIT_FAILURE;
}
Check the Dictionary::load function. The rule is simple: Read, Check, Use. My suggestion is to always start with an infinite loop with the three comments. Then add the relevant code to make the read, then that for checking, and finally use what you just read. Then look for more compact alternatives, if you really need them.
Ah, I just remembered: since you are using VisualStudio, do yourself a favor: don't use precompiled headers. You don't know what they are and, believe me, you won't need them for a very long time. So, create your projects with "Windows Desktop Wizard", don't create a directory for the solution, and in the following dialog select "Empty Project".
If you push all of code to single file, then just make an object of Dictionary and call to function loadDictionary()
Dictionary d;
d.loadDictionary(word);
The code needs to be revised completely. But some quick fixes that are pretty obvious:
The "new" operator returns pointer to the object. In your code you are storing the pointer into a non-pointer variable of the type "Word".
Inside the "while" loop you are trying to access a "Word" object inside another "Word" and then accessing private variables of the class inside "loadDictionary" function.
The data between two classes are duplicated.
Instead of a class for "Word" you can use "struct" which is simpler, however it doesn't really matter if you use classes correctly.

Making a read function that allows spaces in the input (C++)

"Write a class named Person that represents the name and address of a person. Use a string to hold each of these elements. Add operations to read and print Person objects to the code you wrote."
Note I haven't reached the section on access control yet.
#include <iostream>
#include <string>
using std::string;
struct Person
{
string name_var, address_var;
};
std::ostream &print(std::ostream&, const Person &);
std::istream &read(std::istream&, Person &);
std::istream &read(std::istream &is, Person &item)
{
is >> item.name_var >> item.address_var;
return is;
}
std::ostream &print(std::ostream &os, Person &item)
{
os << item.name_var << " " << item.address_var;
return os;
}
With this I can only read single worded names and addresses if I use std::cin as the first argument to read, which isn't very useful. Can you somehow use getline?
You can use:
std::getline(is, item.name_var);
You can also specify a delimiter char as the third argument
Look at one thing: you have used a space to separate name and address. Now, in order to know when the name is over, you need to use some other delimiter for name. Another delimiter means you need to enclose the name in e.g. double quote (") and then, while reading the name, get input until you get the second delimiter.

Reading and then working on stream data

I'm working on a problem 4-6 from Accelerated C++. The question asks that I rewrite the Student_info struct, read() function, and grade() function, so that the final grade is calculated immediately and then stored as the only grade in Student_info.
Previously, the program worked as follows:
read() reads from an input stream and stores the data into a Student_info object
Each object is added to a vector
Once every object is read and added, grade() is called on every Student_info object in the vector
With the new constraints I feel I must combine the read() and grade() functions, so there is no need to store intermediate grades. The problem is when reading from the stream I don't know I have run into the end of file, until I do. When doing this I try to call the grade() function on the end of file data.
I don't see a workaround considering the constraint is to read and then immediately work on the data. How can this be handled?
struct Student_info
{
std::string name;
double final_grade;
};
istream& read(istream& is, Student_info& s)
{
double midterm, final;
is >> s.name >> midterm >> final;
// Error, when EOF is read, grade() will process bad data
s.final_grade = grade(midterm, final);
return is;
}
void main()
{
vector<Student_info> students;
Student_info record;
while (read(cin, record))
students.push_back(record);
}
You can check whether the record was successfully read inside the read function. For example like this:
istream& read(istream& is, Student_info& s)
{
string name;
double midterm, final;
if( is >> name >> midterm >> final ) {
s.name = name;
s.final_grade = grade(midterm, final);
}
return is;
}
Note that you could read directly into s.name as in your original code, but my implementation has transaction semantics: it either reads the whole structure or leaves it alone in case it failed to read all the fields.