File stream into Object vector - c++

class Question
{
public:
void showQuestion();
bool checkAnswer(string givenAnswer);
void showAnswer();
void markCorrect();
private:
string level;
string questionText;
string answerText;
bool correct;
};
class Quiz
{
public:
bool loadQuestions(string dataFileName);
void dumpQuestions();
int deliverQuiz();
private:
vector<Question> questions;
};
I have two classes here, Question, and Quiz, I need to read a text file that will have the questions, answers, ect. after reading the file, I will need to store the variables into a vector. I've tried a couple things, created a Question object vector and stored them in that. However, I believe I need to create a Quiz object and store them in the private vector. I am confused how I can go about storing the variables into the Quiz vector object, or what the syntax for that would look like.
In other words, it makes sense to me to create a Question Object vector and store them in that. However it appears i'll need to create a Quiz Object vector, and store the variables in that, im just not sure how to go about that.
Here is an example of my input file format called questions.txt
S|1|What is the capital of Italy|Rome
S|1|What is the capital of France|Paris

However it appears i'll need to create a Quiz Object vector, and store the variables in that
I don't think you need to do that. In your example you have 1 Quiz which owns many Questions. This makes sense, so I don't think you need to go about creating Quiz object vector.
I need to read a text file that will have the questions, answers, etc. after reading the file, I will need to store the variables into a vector.
Here is a way to populate your vector of Question objects by implementing the Quiz::loadQuestions() method you have defined. You will need to provide access to the private members of your Question object in order to properly retrieve and populate them (provide accessors and mutators).
void Question::setLevel( const int theLevel )
{
level = theLevel;
}
void Question::setQuestion( const std::string & question )
{
questionText = question;
}
And so on. Once you've done that, and given your input file format, you can populate like this.
bool Quiz::loadQuestions( const std::string & fileName )
{
std::ifstream infile(fileName.c_str());
if (infile.is_open())
{
std::string line;
while (std::getline(infile, line))
{
std::stringstream ss(line);
std::string token;
Question temp;
std::getline(ss, token, '|'); // Type (don't care?)
std::getline(ss, token, '|'); // Level
int level = atoi(token.c_str());
temp.setLevel(level);
std::getline(ss, token, '|'); // Question
temp.setQuestion(token);
std::getline(ss, token, '|'); // Answer
temp.setAnswer(token);
// store populated Question object for Quiz
questions.push_back(temp);
}
}
return (!questions.empty());
}

Related

Error when creating a string in a function of class

I create a class named Employee, in private, I have a Name as a string . here is my class declaring:
class Employee
{
string Name;
public:
Employee();
void SetName(string);
void StringToEmployee(string);
~Employee();
}
this is definition of StringToEmployee(string) method:
void Employee::StringToEmployee(string s)
{
char *first = s, *end = s+strlen(s), *last = NULL;
last = find(first, end, ',');
string temp(first, last- first);
SetName(temp);
}
The error occurs when I debug to the line string temp(first, last- first), it's seem to the compiler does not allow me to construct a new string in method. cause I have also changed into string temp; then temp.assign(first, last-first). the error still remain. How could I create a new string in a method?
You should be using iterators and taking advantage of the features of the standard library, rather than raw pointers and C-style string functions. Not only will this give you more idiomatic and easier to understand C++ code, but it will also implicitly resolve many of your errors.
First, the implementation of StringToEmployee should be rewritten as follows:
void Employee::StringToEmployee(std::string s)
{
const std::string temp(s.begin(),
std::find(s.begin(), s.end(), ',');
SetName(temp);
}
But since you are not modifying the s parameter and do not need a copy of it, you should pass it by constant reference:
void Employee::StringToEmployee(const std::string& s)
{
const std::string temp(s.begin(),
std::find(s.begin(), s.end(), ',');
SetName(temp);
}
Also, you should consider redesigning your Employee class. Currently, you have a default constructor that creates an invalid Employee object, and then you have member functions that allow you to turn that invalid Employee object into a valid one by settings its members. Instead, you could have a constructor that did all of this initialization for you, in one step. Not only would your code be cleaner and easier to understand, but it would be more efficient, too!
Perhaps something like:
class Employee
{
std::string Name; // name of this employee
public:
Employee(const std::string& name); // create Employee with specified name
void SetName(const std::string& newName); // change this employee's name
~Employee();
};
Employee::Employee(const std::string& name)
: Name(s.begin(), std::find(s.begin(), s.end(), ','))
{ }
void Employee::SetName(const std::string& newName)
{
Name = std::string(s.begin(), std::find(s.begin(), s.end(), ','));
}
Employee::~Employee()
{ }
A couple of quick notes:
You'll see that I always explicitly write out std:: whenever I use a class from the standard library's namespace. This is a really good habit to get into, and it's not really that hard to type an extra 5 characters. It's particularly important because using namespace std; is a really bad habit to get into.
I pass objects (like strings) that I don't need to modify or have a copy of inside of the method by constant reference. This is both easier to reason about, and also potentially more efficient (because it avoids unnecessary copies).
Inside of the constructor, I have used what may appear to be a funny-looking syntax, involving a colon and some parentheses. This is called a member initialization list, and it's something you should get used to seeing. It's the standard way for a class's constructor to initialize its member variables.
For some reason you want to assing std::string to char*.
Judging from other your code, you want to work with raw char array, so, you need to put correct pointers to first and last like this:
char *first = &s[0], *end = (&s[0]) + strlen(s.c_str()), *last = NULL;
And this part:
string temp(first, last- first);
is incorrect, because last - first is pointer, and, as I understand, you want to use std::string(const char*, size_t) constructor. But instead, you are using iterator-based constructor and system is correctly dying, because first pointer is larger, than second one.
As you see, your method is error-prone. I recommend re-do this part of code, using iterators, like this:
void Employee::StringToEmployee(string s)
{
auto found = find(s.begin(), s.end(), ',');
string temp(s.begin(), found);
SetName(temp);
}

Constructing a vector of custom type with istream_iterator

I want to write and read STL Vector of my class type to binary file, but don't understand what's wrong with istream_iterator.
Project rules disallow to use text files,
same as third-party libraries like Boost.
This is Book.h:
class Book{
public:
Book(const std::vector<Book>& in_one_volumes,const std::string& title,
const std::string& author,const int pagecount,const int price,const std::string& date);
private:
std::vector<Book> inOneVolumes;
std::string title;
std::string author;
int pagecount;
int price;
std::string date;
};
This is write method:
void writeBook(std::vector<Book> books) {
std::ofstream binOut("book.bin", std::ios::binary);
std::copy(books.begin(), books.end(),
std::ostream_iterator<Book>(binOut, "\n"));
}
And i want to read like this:
std::vector<Book> readBooks() {
std::vector<Book> toReturn;
std::ifstream BinIn("book.bin", std::ios::binary);
std::istream_iterator<Book> file_iter(BinIn);
std::istream_iterator<Book> end_of_stream;
std::copy(file_iter, end_of_stream, std::back_inserter(toReturn));
return toReturn;
}
Compiller says -- Book:no appropriate default constructor available.
std::istream_iterator<Book> uses operator>>(std::istream&, Book&) to read data into objects. Since this operator>> requires an existing Book object as parameter (to write the data into), the iterator has to construct one before it can dump the data from the stream into it, and for this it requires a default constructor.
Your Book class does not have one. The easiest solution to the problem would be to give it one.
In the event that this is not an option (e.g., if Book as to guarantee invariants that a default constructor cannot provide), you could introduce an intermediate data transfer class that is default-constructible, can be filled with data via operator>>, and can be converted to Book. Sketch:
class TransferBook {
public:
// To read data from stream
friend std::istream &operator>>(std::istream &in, TransferBook &dest);
// Conversion to Book. Use the non-default Book constructor here.
operator Book() const {
return Book(all the data);
}
private:
// all the data
};
...
std::vector<Book> books;
std::ifstream file;
// Note that books contains Books and the iterator reads TransferBooks.
// No Book is default-constructed, only TransferBooks are.
std::copy(std::istream_iterator<TransferBook>(file),
std::istream_iterator<TransferBook>(),
std::back_inserter(books));
To be sure, this approach is rather cumbersome and essentially duplicates code, and probably it is less hassle to give Book the default-constructor. However, if Book cannot be changed in this way, this is a possible workaround.

no instance of overloaded function

trying to do a project for class, but keep getting the error: no instance of overloaded function matches argument list relating to the implementation of the rows vector. the area that is specifically highlighted is the . operator before push_back and insert.
void holdLines(ifstream in, vector<string> rows) {
string line;
string prevLine;
vector<string> rows;
int lineNumber = 0;
int vectorNumber = 0;
while(true) {
getline(in, line);
if(in.fail()) {
break;
}
lineNumber++;
vectorNumber = lineNumber - 1;
rows.push_back(lineNumber);
rows.insert(prevLine, line);
}
}
You are trying to pass an integer to push_back when a string is required.
It also looks like your local variable "rows" is named the same as your parameter "rows".
http://www.cplusplus.com/reference/vector/vector/push_back/
Your compiler is correct: there is no overload of std::vector<std::string>::push_back that accepts an int, because a std::vector<std::string> stores std::strings, not ints.
It's quite unclear from code alone what you are trying to do, due to the myriad mistakes, but start by replacing your push_back call with something sensible.
There is no method insert with two parameters of type std::string as you are trying to call
rows.insert(prevLine, line);
Also it is not clear what you are trying to do in this statement.
Edit: After you updated yor code nevertheless this statemenet
rows.push_back(lineNumber);
also is wrong because the rows is declared as a vecto of strings. It is not a vector of int and moreover class std::string does not have an appropriate constructor.
But in any case the function does not make sense because you declared a local variable with the same name as the second parameter and tried to fill this local vector that will be deleted after exiting the function
void holdLines(ifstream in, vector<string> rows) {
^^^^^^^^^^^^^^^^^^
string line;
string prevLine;
vector<string> rows;
^^^^^^^^^^^^^^^^^^^^
//..
I think the function should be declared either like
void holdLines(ifstream in, vector<string> &rows);
^^^
or like
vector<string> holdLines(ifstream in);
Take into account that instead of this statements
while(true) {
getline(in, line);
if(in.fail()) {
break;
}
//...
you could write
while ( getline( in, line ) )
{
//...
If you need simply to fill the vector that is passed as the argument then the function can look the following way
void holdLines( std::ifstream &in, std::vector<std::string> &rows )
{
std::string line;
while ( std::getline( in, line ) ) rows.push_back( line );
}

Initializing "const std::string" from "std::istringstream"

I'm trying to parse a file which is in Key<whitespace>Value format. I'm reading the file lines in an std::istringstream object, and I'm extracting a Key string from it. I want to avoid accidentally changing the value of this Key string by making it const.
My best attempt was initializing a temporary VariableKey object, and then making a constant one out of it.
std::ifstream FileStream(FileLocation);
std::string FileLine;
while (std::getline(FileStream, FileLine))
{
std::istringstream iss(FileLine);
std::string VariableKey;
iss >> VariableKey;
const std::string Key(std::move(VariableKey));
// ...
// A very long and complex parsing algorithm
// which uses `Key` in a lot of places.
// ...
}
How do I directly initialize a constant Key string object?
It's arguably better to separate file I/O from processing, and instead of creating a const Key inside the same function - call a line-processing function that takes a const std::string& key parameter.
That said, if you want to continue with your current model, you can simply use:
const std::string& Key = VariableKey;
There's no need to copy or move anything anywhere. Only const std::string members functions will be accessible via Key.
You can avoid the "scratch" variable by extracting the input into a function:
std::string get_string(std::istream& is)
{
std::string s;
is >> s;
return s;
}
// ...
while (std::getline(FileStream, FileLine))
{
std::istringstream iss(FileLine);
const std::string& Key = get_string(iss);
// ...
(Binding the function's result to a const reference extends its lifetime.)

Getting a substream of a stringstream effectively

I need to implement a mechanism where I can initialize a vector of my custom class using a text source, where each line of the source is representing one instance of my class. To achieve this, I implemented the operator >> for my class and stringstream. When I read the source, I go line-by-line and get a substream of my original source, then parse the substream each time. This has three benefits for me. First, this way I can make sure that one line of the text source would represent exactly one instance of my class. Second, as the rest of the line after parsing is ignored, I can safely add any comment in any line of my text source, which would surely get ignored by the parser. And third, I don't need to mention the length of the vector in my original source, since the first time I get a parsing error (I check the fail and bad bits of the stream to confirm this) I know that the vector declaration is over.
To parse line-by-line, I'm using the following code:
std::stringstream fullStream;
std::stringstream lineStream;
std::string str;
bool isValid;
myClass newInstance;
std::vector < myClass > result;
// Fill fullStream from external source (codepart omitted)
isValid = true;
while ( isValid && ! fullStream.eof ( ) ) {
std::getline ( fullStream, str );
lineStream.clear ( );
lineStream.str ( str );
lineStream >> newInstance;
isValid = ! lineStream.fail ( );
if ( isValid ) {
result.push_back ( newInstance );
}
}
Although this code works fine, I'm wondering if there was a better way to achieve the same result. Specially, if there was a more efficient way to extract a line from fullStream to lineStream.
Thanks,
Ádám
First, if the code works, it is really only by chance. The idiomatic
way of handling this is:
std::string line;
while ( std::getline( fullStream, line ) ) {
std::istringstream lineStream( line );
lineStream >> newInstance;
if ( lineStream ) {
result.push_back( newInstance );
} else {
fullStream.setstate( std::ios_base::failbit );
}
}
Checking eof() before a read is rarely useful, and not checking the
results of your getline before using it is almost certainly an error.
Trying to reuse a stringstream is more complex and error prone than
simply creating a new one; there is all sorts of state which may or may
not have to be reset. Streams have a mechanism for memorizing error
state, so you probably want to use this. (If you want to continue using
fullStream for other things after the error, the problem is more
complex, because you've already extracted the line which failed, and you
can't put it back.) And if you're only reading, you should use
std::istringstream, and not std::stringstream (which has a lot of
extra baggage); in general, it's very, very rare to use a bi-directional
stream.
One obvious alternative would be to have your operator>> do line-by-line reading itself, so you don't have to do that externally:
class MyClass {
// some sort of data to demonstrate the idea:
int x;
std::string y;
friend std::istream &operator>>(std::istream &is, MyClass &m) {
std::string temp;
std::getline(is, temp);
std::istringstream buffer(temp);
buffer >> m.x >> m.y;
return is;
}
};
With that, code to read data from a file becomes a little more straightforward:
std::copy(std::istream_iterator<MyClass>(fullStream),
std::istream_iterator<MyClass>(),
std::back_inserter(result));
Edit: if you don't want to build the line-oriented reading directly into the operator>> for MyClass, another possibility is to use a proxy class:
class LineReader {
MyClass object;
public:
operator MyClass() { return object; }
friend std::istream &operator>>(std::istream &is, LineReader &d) {
std::string line;
std::getline(is, line);
std::istringstream buffer(line);
buffer >> d; // delegate to the object's own stream-oriented reader.
}
};
Then when you want to do line-oriented reading, you read objects of the proxy class, but store objects of the original class:
std::vector<MyClass>((std::istream_iterator<LineReader>(some_stream)),
std::istream_iterator<LineReader>());
But, when/if you want to read a stream of objects instead of lines of objects, you use the object's own operator>> directly:
std::vector<MyClass>((std::istream_iterator<MyClass>(stream),
std::istream_iterator<MyClass>());