Reading to end of file with istream_iterator and istream overload - c++

I'm having some trouble reading data from a file into a vector of Orders.
Code:
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <iterator>
using namespace std;
class Purchase;
class Order {
public:
string name;
string address;
vector<Purchase> items;
};
class Purchase {
public:
string product_name;
double unit_price;
int count;
Purchase() {}
Purchase(string pn, double up, int c) :product_name(pn), unit_price(up), count(c) {}
};
istream& operator>>(istream& in, Order& o)
{
string p_name;
double u_price;
int p_count;
getline(in, o.name);
getline(in, o.address);
getline(in, p_name);
in >> u_price >> p_count;
o.items.push_back(Purchase(p_name, u_price, p_count));
return in;
}
ostream& operator<<(ostream& out, const Purchase& p)
{
out << p.product_name << '\n'
<< p.unit_price << '\n'
<< p.count << '\n';
return out;
}
ostream& operator<<(ostream& out, const Order& o)
{
out << '\n' << o.name << '\n'
<< o.address << '\n'
<< o.item << '\n';
return out;
}
int main()
{
cout << "Enter file to read orders from: \n";
string file;
cin >> file;
ifstream is(file.c_str());
istream_iterator<Order> ii(is);
istream_iterator<Order> eos;
ostream_iterator<Order> oo(cout);
vector<Order> orders(ii, eos);
copy(orders.begin(), orders.end(), oo);
}
I have 3 main questions.
1) When I take out the o.item bug in the ostream overload to test output, it only outputs the first entry in the file. The txt file is structured in groups of 5 lines of data that are supposed to be read into vector orders.
Right now the txt file has 10 "orders", but it only reads the first one into the orders vector. I probably need to implement some kind of end of file operation, but I'm not sure how to do this with the istream overload and iterator. This is the biggest problem and if I can figure this out I think I'll probably be okay with the next 2 questions.
2) When that problem is fixed. I will need to deal with the output of o.item (the vector of Purchases in orders which currently can't be output because there is no element being specified). Obviously I need to specify the element to output and I've considered just using a static int and incrementing it, but this would need to be reset for every separate Order, which leads to question 3...
3) If the same name/address are read in as a previous read, I need the program to understand that it is the same "order" being read in and to simply add another object to that Order's Purchases vector rather than creating a new order. I'm thinking about using find() to check if that name already exists in order, and in that case doing nothing with the name/address inputs, but if there is a better way I'd like to know.
Sorry if this is kind of long. If more explanation is needed I'd be happy to elaborate. Any help is appreciated. Thanks.
P.S. Here is an example of input output at the moment if I specify the o.item output to be o.item[0].
Text file has:
John Smith
117 One Tree Hill
Trampoline
600.00
1
//... 9 more Orders like this
Output is:
John Smith
117 One Tree Hill
Trampoline
600.00
1
//... Nothing after this....

Regarding question #3, you could use a multimap instead of a vector.
First, assume you split your Order class up as follows:
class Customer{
public:
string name;
string address;
};
class Purchase {
public:
string product_name;
double unit_price;
int count;
Purchase() {}
Purchase(string pn, double up, int c) :product_name(pn), unit_price(up), count(c) {}
};
class Order {
Customer c;
std::vector<Purchase> p;
};
Now you can simply create a std::multimap<Customer, Purchase>. Adding a customer/purchase pair does exactly what you want: If the customer doesn't already exist, he is added, otherwise the purchase is just added to the existing customer.
Of course, for this to work, you need to define a comparer as well. Simplest way might just be to define operator < for the Customer class. Implement it by comparing the name and disregarding the address.
As for your other questions, avoid mixing getline and stream_iterators. It's not wrong per se, but it gets pretty tricky because getline reads a line at a time, and stream iterators just read to the next whitespace.
Honestly, the C++ IOStreams library is pretty awful to use in general. Since your data format is already cleanly line-separated already, I'd probably just ditch the stream iterators and use getline everywhere.

I haven't looked at your code in detail, but I will give one sentence of advice:
"Do not mix formatted and unformatted input. And in fact, do not use formatted input from files or user input at all."
OK, that was two sentences.

The problem you have is very simple. In fact your code is pretty clear :)
All what you have to add are those simple lines:
istream& operator>>(istream& in, Order& o)
{
string p_name;
double u_price;
int p_count;
getline(in, o.name);
getline(in, o.address);
getline(in, p_name);
in >> u_price >> p_count;
//
while(in.peek() == '\n' || in.peek() == ' ')
{
in.ignore();
}
//
o.items.push_back(Purchase(p_name, u_price, p_count));
return in;
}
The reason is that when using >> it leaves the newline character in the stream unlike getline. You can search Stackoverflow about streams in general there are a lot of great explanations about the issue.
Also, you don't have anything called item in Order. What you have is a vector of Purchase:
ostream& operator<<(ostream& out, const Order& o)
{
out << '\n' << o.name << '\n'
<< o.address << '\n';
//
for(vector<Purchase>::const_iterator i = o.items.begin();
i != o.items.end(); i++)
{
out << *i << '\n';
}
//
return out;
}

Related

Read from comma separated file into vector of objects

I have a done a simple C++ program to gain knowledge in C++. It's a game which stores and reads in the end to a file. Score, Name etc..
At each line in the file the content for a Player object is stored.
Ex: ID Age Name etc.
I now wanted to change to comma separation in the file but then I faced the issue how to read each line and write the Player object into a vector of Player objects std::vector correct.
My code today is like.
std::vector<Player> readPlayerToVector()
{
// Open the File
std::ifstream in("players.txt");
std::vector<Player> players; // Empty player vector
while (in.good()) {
Player temp; //
in >> temp.pID;
....
players.push_back(temp);
}
in.close();
return players;
}
How should I change this code to be compatible with comma separation. Not it works with space separation with the overload of >>.
Be aware that I am a beginner in C++. I've tried looking of the examples where std::getline(ss, line) with stringstream is used but I can't figure out a good way to assign the Player object with that method.
I will try to help and explain you all steps. I will first show a little bit of theory and then some easy solution, some alternative solutions and the C++ (object-oriented) approach.
So, we will go from super easy to more modern C++ solution.
Let’s start. Assume that you have a of player with some attributes. Attributes could be for example: ID Name Age Score. If you store this data in a file, it could look like:
1 Peter 23 0.98
2 Carl 24 0.75
3 Bert 26 0.88
4 Mike 24 0.95
But at some point in time, we notice that this nice and simple format will not work any longer. The reason is that formatted input functions with the extractor operator >> will stop the conversion at a white space. And this will not work for the following example:
1 Peter Paul 23 0.98
2 Carl Maria 24 0.75
3 Bert Junior 26 0.88
4 Mike Senior 24 0.95
Then the statement fileStream >> id >> name >> age >> score; will not work any longer, and everything will fail. Therefore storing data in a CSV (Comma Separated Values) format is widely chosen.
The file would then look like:
1, Peter Paul, 23, 0.98
2, Carl Maria, 24, 0.75
3, Bert Junior, 26, 0.88
4, Mike Senior, 24, 0.95
And with that, we can clearly see, what value belongs to which attribute. But unfortunately, this will make reading more difficult. Because you do need to follow 3 steps now:
Read a complete line as a std::string
Split this string into substrings using the comma as a separator
Convert the substrings to the required format, for example from string to number age
So, let us solve this step by step.
Reading a complete line is easy. For this we have the function std::getline. It will read a line (at text until the end of the line character ‘\n’) from a stream (from any istream, like std::cin, an std::ifstream or also from an std::istringstream) and store it in a std::string variable. Please read a description of the function in the CPP Reference here.
Now, splitting a CSV string in its parts. There are so many methods available, that it is hard to tell what is the good one. I will also show several methods later, but the most common approach is done with std::getline. (My personal favorite is the std::sregex_token_iterator, because it fits perfectly into the C++ algorithm world. But for here, it is too complex).
OK, std::getline. As you have read in the CPP reference, std::getline reads characters until it finds a delimiter. If you do not specify a delimiter, then it will read until the end of line \n. But you can also specify a different delimiter. And this we will do in our case. We will choose the delimiter ‘,’.
But, additional problem, after reading a complete line in step 1, we have this line in a std::string. And, std::getline wants to read from a stream. So, the std::getline with a comma as delimiter cannot be used with a std::string as source. Fortunately also here is a standard approach available. We will convert the std::string into a stream, by using a std::istringstream. You can simply define a variable of this type and pass the just read string as parameter to its constructor. For example:
std::istringstream iss(line);
And now we can use all iostream functions also with this “iss”. Cool. We will use std::getline with a ‘,’ delimiter and receive a substring.
The 3rd and last is unfortunately also necessary. Now we have a bunch of substrings. But we have also 3 numbers as attributes. The “ID” is an unsigned long, the “Age” is an int and the “Score” is a double, So we need to use string conversion functions to convert the substring to a number: std::stoul, std::stoi and std::stod. If the input data is always OK, then this is OK, but if we need to validate the input, then it will be more complicated. Let us assume that we have a good input.
Then, one of really many many possible examples:
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <string>
struct Player {
unsigned long ID{};
std::string name{};
int age{};
double score{};
};
// !!! Demo. All without error checking !!!
int main() {
// Open the source CSV file
std::ifstream in("players.txt");
// Here we will store all players that we read
std::vector<Player> players{};
// We will read a complete line and store it here
std::string line{};
// Read all lines of the source CSV file
while (std::getline(in, line)) {
// Now we read a complete line into our std::string line
// Put it into a std::istringstream to be able to extract it with iostream functions
std::istringstream iss(line);
// We will use a vector to store the substrings
std::string substring{};
std::vector<std::string> substrings{};
// Now, in a loop, get the substrings from the std::istringstream
while (std::getline(iss, substring, ',')) {
// Add the substring to the std::vector
substrings.push_back(substring);
}
// Now store the data for one player in a Player struct
Player player{};
player.ID = std::stoul(substrings[0]);
player.name = substrings[1];
player.age = std::stoi(substrings[2]);
player.score = std::stod(substrings[3]);
// Add this new player to our player list
players.push_back(player);
}
// Debug output
for (const Player& p : players) {
std::cout << p.ID << "\t" << p.name << '\t' << p.age << '\t' << p.score << '\n';
}
}
You see, it is getting more complex.
If you are more experienced, then you can use also other mechanisms. But then you need to understand the difference between formatted an unformatted input and need to have little bit more practice. This is complex. (So, do not use that in the beginning):
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <string>
struct Player {
unsigned long ID{};
std::string name{};
int age{};
double score{};
};
// !!! Demo. All without error checking !!!
int main() {
// Open the source CSV file
std::ifstream in("r:\\players.txt");
// Here we will store all players that we read
Player player{};
std::vector<Player> players{};
char comma{}; // Some dummy for reading a comma
// Read all lines of the source CSV file
while (std::getline(in >> player.ID >> comma >> std::ws, player.name, ',') >> comma >> player.age >> comma >> player.score) {
// Add this new player to our player list
players.push_back(player);
}
// Debug output
for (const Player& p : players) {
std::cout << p.ID << "\t" << p.name << '\t' << p.age << '\t' << p.score << '\n';
}
}
As said, do not use in the beginning.
But, what you should try to learn and understand is: C++ is an object oriented language. This means we do not only put the data into the Player struct, but also the methods that operate on this data.
And those are at the moment just input and output. And as you already know, input and output is done using iostream-functionality with the extractor operator >> and inserter operator <<. But, how to do this? Our Player struct is a custom type. It has no build in >> and << operator.
Fortunately, C++ is a powerful language and allows us to add such functionality easily.
The signature of the struct would then look like:
struct Player {
// The data part
unsigned long ID{};
std::string name{};
int age{};
double score{};
// The methods part
friend std::istream& operator >> (std::istream& is, Player& p);
friend std::ostream& operator << (std::ostream& os, const Player& p);
};
And, after writing the code for these operators using the above-mentioned method, we will get:
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <string>
struct Player {
// The data part
unsigned long ID{};
std::string name{};
int age{};
double score{};
// The methods part
friend std::istream& operator >> (std::istream& is, Player& p) {
std::string line{}, substring{}; std::vector<std::string> substrings{};
std::getline(is, line);
std::istringstream iss(line);
// Read all substrings
while (std::getline(iss, substring, ','))
substrings.push_back(substring);
// Now store the data for one player in the given Player struct
Player player{};
p.ID = std::stoul(substrings[0]);
p.name = substrings[1];
p.age = std::stoi(substrings[2]);
p.score = std::stod(substrings[3]);
return is;
}
friend std::ostream& operator << (std::ostream& os, const Player& p) {
return os << p.ID << "\t" << p.name << '\t' << p.age << '\t' << p.score;
}
};
// !!! Demo. All without error checking !!!
int main() {
// Open the source CSV file
std::ifstream in("r:\\players.txt");
// Here we will store all players that we read
Player player{};
std::vector<Player> players{};
// Read all lines of the source CSV file into players
while (in >> player) {
// Add this new player to our player list
players.push_back(player);
}
// Debug output
for (const Player& p : players) {
std::cout << p << '\n';
}
}
It is simply reusing everything from what we learned above. Just put it at the right place.
We can even go one step ahead. Also the player list, the ste::vector<Player> can be wrapped in a class and amended with iostream-functionality.
By knowing all of the above, this will be really simple now. See:
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <string>
struct Player {
// The data part
unsigned long ID{};
std::string name{};
int age{};
double score{};
// The methods part
friend std::istream& operator >> (std::istream& is, Player& p) {
char comma{}; // Some dummy for reading a comma
return std::getline(is >> p.ID >> comma >> std::ws, p.name, ',') >> comma >> p.age >> comma >> p.score;
}
friend std::ostream& operator << (std::ostream& os, const Player& p) {
return os << p.ID << "\t" << p.name << '\t' << p.age << '\t' << p.score;
}
};
struct Players {
// The data part
std::vector<Player> players{};
// The methods part
friend std::istream& operator >> (std::istream& is, Players& ps) {
Player player{};
while (is >> player) ps.players.push_back(player);
return is;
}
friend std::ostream& operator << (std::ostream& os, const Players& ps) {
for (const Player& p : ps.players) os << p << '\n';
return os;
}
};
// !!! Demo. All without error checking !!!
int main() {
// Open the source CSV file
std::ifstream in("players.txt");
// Here we will store all players that we read
Players players{};
// Read the complete CSV file and store everything in the players list at the correct place
in >> players;
// Debug output of complete players data. Ultra short.
std::cout << players;
}
I would be happy, if you could see the simple and yet powerful solution.
At the very end, as promised. Some further methods to split a string into substrings:
Splitting a string into tokens is a very old task. There are many many solutions available. All have different properties. Some are difficult to understand, some are hard to develop, some are more complex, slower or faster or more flexible or not.
Alternatives
Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.
Using old style std::strtok function. Maybe unsafe. Maybe should not be used any longer
std::getline. Most used implementation. But actually a "misuse" and not so flexible
Using dedicated modern function, specifically developed for this purpose, most flexible and good fitting into the STL environment and algortithm landscape. But slower.
Please see 4 examples in one piece of code.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>
using Container = std::vector<std::string>;
std::regex delimiter{ "," };
int main() {
// Some function to print the contents of an STL container
auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '\n'; };
// Example 1: Handcrafted -------------------------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Search for comma, then take the part and add to the result
for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {
// So, if there is a comma or the end of the string
if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {
// Copy substring
c.push_back(stringToSplit.substr(startpos, i - startpos));
startpos = i + 1;
}
}
print(c);
}
// Example 2: Using very old strtok function ----------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
c.push_back(token);
}
print(c);
}
// Example 3: Very often used std::getline with additional istringstream ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Put string in an std::istringstream
std::istringstream iss{ stringToSplit };
// Extract string parts in simple for loop
for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
;
print(c);
}
// Example 4: Most flexible iterator solution ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
//
// Everything done already with range constructor. No additional code needed.
//
print(c);
// Works also with other containers in the same way
std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
print(c2);
// And works with algorithms
std::deque<std::string> c3{};
std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));
print(c3);
}
return 0;
}
Happy coding!
I provided a similar solution here:
read .dat file in c++ and create to multiple data types
#include <iostream>
#include <sstream>
#include <vector>
struct Coefficients {
unsigned A;
std::vector<double> B;
std::vector< std::vector<double> > C;
};
std::vector<double> parseFloats( const std::string& s ) {
std::istringstream isf( s );
std::vector<double> res;
while ( isf.good() ) {
double value;
isf >> value;
res.push_back( value );
}
return res;
}
void readCoefficients( std::istream& fs, Coefficients& c ) {
fs >> c.A;
std::ws( fs );
std::string line;
std::getline( fs, line );
c.B = parseFloats( line );
while ( std::getline( fs, line ) ) {
c.C.push_back( parseFloats( line ) );
}
}
This one also might apply:
Best way to read a files contents and separate different data types into separate vectors in C++
std::vector<int> integers;
std::vector<std::string> strings;
// open file and iterate
std::ifstream file( "filepath.txt" );
while ( file ) {
// read one line
std::string line;
std::getline(file, line, '\n');
// create stream for fields
std::istringstream ils( line );
std::string token;
// read integer (I like to parse it and convert separated)
if ( !std::getline(ils, token, ',') ) continue;
int ivalue;
try {
ivalue = std::stoi( token );
} catch (...) {
continue;
}
integers.push_back( ivalue );
// Read string
if ( !std::getline( ils, token, ',' )) continue;
strings.push_back( token );
}
You could separate each variable by line rather than comma. I find this approach much more simple as you can use the getline function.
Have a read of the documentation of ifstream/ofstream. I've done several projects based of this documentation alone!
C++ fstream reference

Reading custom file formats in C++

I read configuration files of the following format into my C++ code:
# name score
Marc 19.7
Alex 3.0
Julia 21.2
So far, I have adapted a solution found here: Parse (split) a string in C++ using string delimiter (standard C++). For example, the following code snippet reads in the file line by line, and for each line calls parseDictionaryLine, which discards the first line, splits the string as described in the original thread, and inserts the values into a (self-implemented) hash table.
void parseDictionaryLine(std::string &line, std::string &delimiter, hash_table &table) {
size_t position = 0;
std::string name;
float score;
while((position = line.find(delimiter)) != std::string::npos) {
name = line.substr(0, position);
line.erase(0, position + delimiter.length());
score = stof(line);
table.hinsert(name, score);
}
}
void loadDictionary(const std::string &path, hash_table &table) {
std::string line;
std::ifstream fin(path);
std::string delimiter = " ";
int lineNumber = 0;
if(fin.is_open()) {
while(getline(fin, line)) {
if(lineNumber++ < 1) {
continue; // first line
}
parseDictionaryLine(line, delimiter, table);
}
fin.close();
}
else {
std::cerr << "Unable to open file." << std::endl;
}
}
My question would be, is there a more elegant way in C++ to achieve this task? In particular, is there (1) a better split function as for example in Python, (2) a better method to test if a line is a comment line (starting with #), like startsWith (3) potentially even in iterator that handles files similar to a context manager in Python and makes sure the file will actually be closed? My solution works for simple cases shown here but becomes more clunky with more complicated variations such as several comment lines at unpredictable positions and more parameters. Also, it worries me that my solution does not check if the file actually agrees with the prescribed format (two values per line, first is string, second is float). Implementing these checks with my method seems very cumbersome.
I understand there is JSON and other file formats with libraries made for this use case, but I am dealing with legacy code and cannot go there.
I will try to answer all your questions.
First for splitting a string, you should not use the linked question/answer. It is from 2010 and rather outdated. Or, you need to scroll at the very bottom. There you will find more modern answers.
In C++ many things are done with iterators. Because a lot of algorithms or constructors in C++ work with iterators. So, the better approch for splitting a string is to use iterators. This will then always result in a one liner.
Background. A std::string is also a container. And you can iterate over elements like for example words or values in it. In case of space separated values you can use the std::istream_iterator on a std::istringstream. But since years there is a dedicated iterator for iterating of patterns in a string:
The std::sregex_token_iterator. And because it is specifically designed for that purpuse, it should be used.
Ans if it is used for splitting the strings, the overhead of using regexes is also minimal. So, you may split on strings, commas, colons or whatever. Example:
#include <iostream>
#include <string>
#include <vector>
#include <regex>
const std::regex re(";");
int main() {
// Some test string to be splitted
std::string test{ "Label;42;string;3.14" };
// Split and store whatever number of elements in the vector. One Liner
std::vector data(std::sregex_token_iterator(test.begin(), test.end(), re, -1), {});
// Some debug output
for (const std::string& s : data) std::cout << s << '\n';
}
So, regardless of the number of patterns, it will copy all data parts into the std::vector.
So, now you have a one liner solution for splitting strings.
For checking. if the first character is a string, you may use
the index operator (if (string[0] == '#'))
or, the std::string's front function (if (string.front() == '#'))
or again a regex
But, here you need to be careful. The string must not be empty, so, better write:
if (not string.empty() and string.front() == '#')
Closing file or iterating over files.
If you use a std::ifstream then the constructor will open the file for you and the destructor will automatically close it, when the stream variable rund out of scope. The typical pattern here is:
// Open the file and check, if it coud be opened
if (std::iftsream fileStream{"test.txt"};fileStream) {
// Do things
} // <-- This will close the file automatically for you
Then, in general you shoud use a more object oriented approach. Data, and methods operating on this data, should be encapsulated in one class. Then you would overwrite the extractor operatoe >> and the inserter operator << to read and write the data. This, because only the class should know, how to handle the data. And if you decide to use a different mechanism, modify your class and the rest of the outside world will still work.
In your example case, input and output is that simple, that easiest IO will work. No splitting of string necessary.
Please see the following example.
And note especially the only few statements in main.
If you change something inside the classes, it will simple continue to work.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
// Data in one line
struct Data {
// Name and score
std::string name{};
double score{};
// Extractor and inserter
friend std::istream& operator >> (std::istream& is, Data& d) { return is >> d.name >> d.score; }
friend std::ostream& operator << (std::ostream& os, const Data& d) { return os << d.name << '\t' << d.score; }
};
// Datbase, so all data from the source file
struct DataBase {
std::vector<Data> data{};
// Extractor
friend std::istream& operator >> (std::istream& is, DataBase& d) {
// Clear old data
d.data.clear(); Data element{};
// Read all lines from source stream
for (std::string line{}; std::getline(is, line);) {
// Ignore empty and command lines
if (not line.empty() and line.front() != '#') {
// Call extractor from Data class end get the data
std::istringstream(line) >> element;
// And save new data in the datbase
d.data.push_back(std::move(element));
}
}
return is;
}
// Inserter. Output all data
friend std::ostream& operator << (std::ostream& os, const DataBase& d) {
std::copy(d.data.begin(), d.data.end(), std::ostream_iterator<Data>(os, "\n"));
return os;
}
};
int main() {
// Open file and check, if it is open
if (std::ifstream ifs{ "test.txt" }; ifs) {
// Our database
DataBase db{};
// Read all data
ifs >> db;
// Debug output show all data
std::cout << db;
}
else std::cerr << "\nError: Could not open source file\n";
}
You can use operator>> to split at delimiters for you, like this:
#include <iostream>
#include <sstream>
#include <unordered_map>
std::istringstream input{
"# name score\n"
"Marc 19.7\n"
"Alex 3.0\n"
"Julia 21.2\n"
};
auto ReadDictionary(std::istream& stream)
{
// unordered_map has O(1) lookup, map has n(log n) lookup
// so I prefer unordered maps as dictionaries.
std::unordered_map<std::string, double> dictionary;
std::string header;
// read the first line from input (the comment line or header)
std::getline(stream, header);
std::string name;
std::string score;
// read name and score from line (>> will split at delimiters for you)
while (stream >> name >> score)
{
dictionary.insert({ name, std::stod(score) });
}
return dictionary;
}
int main()
{
auto dictionary = ReadDictionary(input); // todo replace with file stream
// range based for loop : https://en.cppreference.com/w/cpp/language/range-for
// captured binding : https://en.cppreference.com/w/cpp/language/structured_binding
for (const auto& [name, score] : dictionary)
{
std::cout << name << ": " << score << "\n";
}
return 0;
}

Reading specific words from a file and storing them in an object

I'm having trouble coding and conceptualizing this project I was assigned. I've looked around for answers to this issue but had little to no luck, maybe it's really obvious. I'm supposed to prompt the user to a filename, the file is assume to have the following format:
Animal:
Name: [value]
Noise: [value]
Legs: [value]
(with no spaces in between)
It should be able to read as many "animal objects" as there are in the file and store them in an animal object class that has 3 parameters (name, noise, legs).
My issue is mostly during the reading in of the file, I can't figure out a good method for reading the file AND storing the information. Here is the code I currently have. Any help with the code I currently have and ideas for storing the values. Sorry if I explained anything poorly, please ask to clarify if I did, thank you in advance.
cout << "Enter the file name: ";
string fileName;
getline(cin, fileName);
cout << endl;
try
{
ifstream animalFile(fileName);
if (!animalFile.good()) // if it's no good, let the user know and let the loop continue to try again
{
cout << "There was a problem with the file " << fileName << endl << endl;
continue;
}
string line;
while (animalFile >> line) // To get you all the lines.
{
getline(animalFile, line); // Saves the line in STRING.
cout << line << endl; // Prints our STRING.
}
}
catch (...)
{
cout << "There was a problem with the file " << fileName << endl << endl;
}
If you're really binded with this file format, consider doing the following to read the data and store it:
#1. Define a class Animal to represent an animal:
struct Animal
{
std::string name;
int legs;
int noise;
}
#2. Define an istream& operator >> (istream&, Animal&) to read one object of this type and check for the correctness of the input.
std::istream& operator >> (std::istream& lhs, Animal& rhs)
{
std::string buf;
lhs >> buf >> buf >> rhs.name >> buf >> rhs.noise >> buf >> rhs.legs;
}
#3. Use std::copy and std::istream_iterator to read all the values from the file to std::vector:
std::istream_iterator<Animal> eos;
std::istream_iterator<Animal> bos(animalFile);
std::vector<Animal> zoo;
std::copy(bos, eos, std::back_inserter(zoo));
This code has no checking for input errors, which may easily be added into istream& operator >> (istream&, Animal&).

C++ structures/arrays [duplicate]

This question already has answers here:
Why does std::getline() skip input after a formatted extraction?
(5 answers)
Closed 9 years ago.
I'm creating a program that stores basic data. After inputing data for the first 'person', the program simultaneously loops to the second instance of i without accepting any input. This is hard to explain but I hope someone can help me out. I have a feeling that getline and cin.ignore are causing these problems. I only came across them on this website.
struct info {
string name;
string address;
int phone;
};
int main(){
int input;
cout<<"How many people do you want on the list" <<endl;
cin>>input;
info arr[input];
for(int i=0;i<input;i++){
cout<<"Enter name for person " <<i+1 <<": ";
getline(cin,arr[i].name);
cin.ignore(1000,'\n');
cout<<"Enter the address of " <<arr[i].name <<endl;
getline(cin,arr[i].address);
cin.ignore(1000, '\n');
cout<<"Enter phone number of " <<arr[i].name <<endl;
cin>>arr[i].phone;
cout<<endl;
}
}
Mixing formatted input with unformatted input is always asking for trouble. When you extract input into the stream, a newline is always appended. The newline character is the delimiter for std::getline(), meaning whilst extraction is being performed, if std::getline() finds a newline, it discards that character and stops input.
This is the problem you're running into. After the last formatted extraction cin >> input, the newline is left in the input stream. std::getline() (unlike formatted input functions) do not discard leading whitespace, so it is prone to the disadvantage I explained above.
To solve this issue, you need to discard leading whitespace manually, so the the non-whitespace input is ready to be extracted by the time std::getline() is invoked:
std::getline(std::cin >> std::ws, arr[i].name);
// ^^^^^^^^^^^^^^^^^^^
std::ws is manipulator which extracts and discards leading whitespace. This should be satisfactory for your purposes.
The following are suggestions that I advise you take. They lead to a substantially cleaner, clearer code structure. And it also reduces a lot of headaches:
1. Your class should have its own inserter/extractor.
You should define your own inserter/extractor to encapsulate I/O semantics for your class. It facilitates a lot of the trouble of manual extraction. Inserters/extractors are typically implemented as friends of the target class so that it may access private data members. Your class in particular has no private data members, but this is still an idiomatic technique nontheless:
struct info
{
string name;
string address;
int phone;
friend std::ostream& operator<<(std::ostream&, const info&);
friend std::istream& operator>>(std::istream&, info&);
};
Inserters/extractors are global functions because they are being defined in terms of (not only) the target class, but also the stream object itself.
2. Use std::vector<T> rather than raw C-style arrays
The following is wrong:
cin >> input;
info arr[input];
This code instantiates an array at compile-time with a size known only at runtime. The possibility of your compiler accepting this code fully depends on how standard-conformant it is. GCC has a non-standard extension which welcomes code like that, but it is non-standard even so and shouldn't be used.
If you need an array of objects whose size is known at runtime, use a vector of those objects. A vector class is already provided for you through the Standard library:
std::cin >> input;
std::vector<info> arr(input);
In fact, the size is superfluous since the vector will grow dynamically as more and more elements are created. You can simply default-construct the vector (unless you have a good reason to specify the size, in which case that many info objects will be default-constructed in the internal buffer of the vector).
std::vector<info> arr;
This is mostly it. Here is an example of your program that applies these two suggestions:
#include <iostream>
#include <vector>
struct info
{
std::string name;
std::string address;
int phone;
friend std::ostream& operator<<(std::ostream&, const info&);
friend std::istream& operator>>(std::istream&, info&);
};
std::ostream& operator<<(std::ostream& os, const info& obj)
{
return os << "Person's name is: " << obj.name << std::endl
<< obj.name << "'s address is: " << obj.address << std::endl
<< obj.name << "'s phone number is: " << obj.phone << std::endl;
}
std::istream& operator>>(std::istream& is, info& obj)
{
std::cout << "Enter name for person " << ": ";
std::getline(is >> std::ws, obj.name);
std::cout << "Enter the address of " << obj.name << std::endl;
std::getline(is >> std::ws, obj.address);
std::cout << "Enter phone number of " << obj.name << std::endl;
std::cin >> obj.phone;
std::cout << endl;
return is;
}
int main()
{
int input;
std::cout << "How many people do you want on the list" << std::endl;
std::cin >> input; // just as a formality, but really the user can enter
// as much as they want.
// The value of input will be ignored.
std::vector<info> arr;
info obj;
while (std::cin >> obj)
{
arr.push_back(obj);
}
for (const auto& item : arr)
std::cout << item << std::endl;
}
If you have any questions, please leave them as a comment and I will try to help you as best as possible.

Read in from file into structure

I want to read in from txt file into structure using fstream.
I save the data to the file in the way shown below:
To read the data i tried some cheeky stuff with getlines or tabsin<
struct tab{
int type,use;
string name, brand;
};
tab tabs[500];
ofstream tabsout;
tabsout.open("tab.txt", ios::out);
for (int i = 0; i < 500; i++){
if (tabs[i].use==1){
tabsout << tabs[i].type << " " << tabs[i].name << " " << tabs[i].brand << "\n";
}
}
tabsout.close();
//input part that fails me :(
int i=0;
ifstream tabsin;
tabsin.open("tab.txt", ios::in);
if (tabsin.is_open()){
while(tabsin.eof() == false)
{
tabsin >> tabs[i].type>>tabs[i].name>>tabs[i].brand;
i++
}
tabsin.close();
You usually want to overload operator>> and operator<< for the class/struct, and put the reading/writing code there:
struct tab{
int type,use;
string name, brand;
friend std::istream &operator>>(std::istream &is, tab &t) {
return is >> t.type >> t.name >> t.brand;
}
friend std::ostream &operator<<(std::ostream &os, tab const &t) {
return os << t.type << " " << t.name << " " << t.brand;
}
};
Then you can read in a file of objects like:
std::ifstream tabsin("tab.txt");
std::vector<tab> tabs{std::istream_iterator<tab>(tabsin),
std::istream_iterator<tab>()};
....and write out the objects like:
for (auto const &t : tabs)
tabsout << t << "\n";
Note that (like any sane C++ programmer) I've used a vector instead of an array, to (among other things) allow storing an arbitrary number of items, and automatically track how many are actually being stored.
For starters, do not use .eof() to control your loop: it doesn't work. Instead, use the stream's state after reading:
int type;
std::string name, brand;
while (in >> type >> name >> brand) {
tabs.push_back(tab(type, name, brand));
}
If your name or brand contain spaces, the above won't work and you will need to write a format where you can know when to stop abd read correspondingly, e.g., using std::getline().
You might also consider wrapping the logic to read or write an object by suitable operators.
istream& getline (istream& is, string& str, char delim);
Take a look at the third parameter, you can use std::getline to parse your line. But that is definitely not the best way to serialize objects. Instead of using a text file, you should use a byte stream.