It's been a long time since I've used C++, been using Javascript in my free time and now I'm not sure exactly what I remember.
Basically I just need to separate a string into parts by looking at the spaces.
All the links I've seen are homemade functions, but I could have sworn there was a way to do it using the standard library by using streams, but again, I'm having a tough time recalling it and my google results aren't helping either.
Keep in mind that it isn't a stream I am taking from, it's just a string like "Bob Accountant 65 retired" and I have to extract each item in the string to its own data field. I've been messing with ifstreams and ofstreams but I'm not even sure what I'm doing, having forgot the syntax for it.
std::strtok is the C-style way to do it. You might be thinking of using a std::stringstream, e.g.:
#include <sstream>
#include <string>
#include <iostream>
int main() {
std::string input = "foo bar baz quxx\nducks";
std::stringstream ss(input);
std::string word;
while (ss >> word) {
std::cout << word << '\n';
}
}
When run, that displays:
foo
bar
baz
quxx
ducks
If you want to read data from a std::stringstream (or any type of std::istream, really) into a specific data type, you can follow #JerryCoffin's excellent suggestion of overloading the stream operator>> for your data type:
#include <sstream>
#include <string>
#include <iostream>
struct Employee {
std::string name;
std::string title;
int age;
std::string status;
};
std::istream& operator>>(std::istream &is, Employee &e) {
return is >> e.name >> e.title >> e.age >> e.status;
}
int main() {
std::string input = "Bob Accountant 65 retired";
std::stringstream ss(input);
Employee e;
ss >> e;
std::cout << "Name: " << e.name
<< " Title: " << e.title
<< " Age: " << e.age
<< " Status: " << e.status
<< '\n';
}
You can do it without an explicit loop like this:
string s = "Bob Accountant 65 retired";
vector<string> vs;
istringstream iss(s);
copy(istream_iterator<string>(iss), istream_iterator<string>(), back_inserter(vs));
std::copy reads everything from the string stream created on the third line, and pushes it into the vector created on the second line.
Here is a demo on ideone.
From the looks of things, you're trying to read a logical record from a string. For that, I'd do something like this:
struct record {
std::string name;
std::string position;
int age;
std::string status;
};
std::istream &operator>>(std::istream &is, record &r) {
return i >> r.name >> r.position >> r.age >> r.status;
}
This lets you read data from a stringstream to a record of the specified fields. It'll also, incidentally, let you read record objects from other types of streams as well (e.g., from a file using an fstream).
Related
Question may seem like a duplicate but I have researched and found nothing that would actually answer my question.
So I have a task that let's the user input groceries as a string, the weight and price as doubles. Simple enough, right? Well, the problem is that the user will enter it in the following format:
Enter product 1: Potato; 5 kg; 49 kr;
Enter product 2: Carrot; 0.5 kg; 13 kr;
Enter product 3: Onion; 0.1 kg; 2 kr;
And then the products will be sorted in a specific manner. However that is not what I need help with, I have a problem with reading in only "Potato", "5" and "49" and store them in seperate variables.
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
using namespace std;
struct Product_Type
{
string name;
double weight;
double price;
};
void get (Product_Type & p)
{
cin >> p.name;
cin.ignore(1);
cin >> p.weight;
cin.ignore(2);
cin >> p.price;
cin.ignore(2);
}
If I do it like this, I will store "Potato;" and not "Potato" in my name variable. So my question is how do I manipulate a string to only read to a certain index and store the content in a variable? I don't want to read the actual semi-colons. Is there a simple way to do this?
You need to understand how formatted input and unformatted input works.
Please read here about it.
You do not need ignore at all. You can do all with simple statements.
But the precondition is that the input matches your specification. Any input of wrong data leads to a problem. So, let's assume that the input follows the above pattern. OK.
Regarding your problem, where you read "potato;" instead of "potato". This is because you use a formatted input function >> to read a string. And this function will stop, if it sees a white space and not before. It will also ignore leading white space by default, but that is not important here.
To solve the problem to read a string until a certain delimiter, you can use the unformatted input function std::getline. Please see here.
So, to get the name, you may write std::getline(std::cin, p.name, ';');. This will read the name from the input stream, including the semicolon, but store the pure name, without the semicolon in the string.
Next we are talking about reading 5 kg; 49 kr. We want to have the "weight". We can simply use formatted input function >> to get it. There is a white space after that, so, very simple. You can write std::cin >> p.weight; to get it.
Next we have to read kg; 49 kr. So, we will use a dummy string and with that can read the "kg;" which we do simple not use. So, with a dummy string "s1", we can write std::cin >> s1;. The kg; will be gone from the stream.
Now we see 49 kr;. Reading the price is again simple using >>. We will write std::cin >> p.price; and have it.
Last but not least we need to read the rest of the line, including the enter. For that we use again std::getline. No need to specify any delimiter, because the standard delimiter is already '\n'.
Then the full code looks like the below:
#include <iostream>
#include <string>
struct Product_Type
{
std::string name;
double weight;
double price;
};
void get(Product_Type& p)
{
std::string s1, s2;
std::getline(std::cin, p.name, ';');
std::cin >> p.weight;
std::cin >> s1;
std::cin >> p.price;
std::getline(std::cin, s2);
}
int main() {
Product_Type pt;
get(pt);
std::cout << pt.name << " --> " << pt.weight << " --> " << pt.price << '\n';
return 0;
}
But this is not considered to be good. There may be errors in the input and with that the failbit of std::cin would be set and you cannot continue to read.
Therefore, normally, we would read first the complete line with std::getline which will most likely always work. Then, we "convert" the string to a stream using an std::istringstream to be able to extract the data from there. If the complete line has an error, then only the temporary std::istringstream will be gone. So this approach is a little bit more robust.
This could then look like:
#include <iostream>
#include <string>
#include <sstream>
struct Product_Type
{
std::string name;
double weight;
double price;
};
void get(Product_Type& p)
{
std::string line, s1, s2;
std::getline(std::cin, line);
std::istringstream iss(line);
std::getline(iss, p.name, ';');
iss >> p.weight;
iss >> s1;
iss >> p.price;
std::getline(iss, s2);
if (not iss)
std::cerr << "\nError: Problem with input\n\n";
}
int main() {
Product_Type pt;
get(pt);
std::cout << pt.name << " --> " << pt.weight << " --> " << pt.price << '\n';
return 0;
}
And a little bit more advanced with chaining the io-functions, we can write
#include <iostream>
#include <string>
#include <sstream>
struct Product_Type
{
std::string name;
double weight;
double price;
};
void get(Product_Type& p)
{
std::string line, s1, s2;
std::getline(std::cin, line);
std::istringstream iss(line);
std::getline(std::getline(iss, p.name, ';') >> p.weight >> s1 >> p.price, s2);
if (not iss)
std::cerr << "\nError: Problem with input\n\n";
}
int main() {
Product_Type pt;
get(pt);
std::cout << pt.name << " --> " << pt.weight << " --> " << pt.price << '\n';
return 0;
}
All the above is very simplified, but may give you a starting point for better ideas.
I have a file, that contain student name, id, age and number of this student. I want to read the file and for example if the number of student is 2 delete all 3 precedent lines corresponding to this student.
My txt look like this
Marc 45646564 18 1 Jean 4563213 21 2
Paul 45654 22 4 Alice 45948 20 5
while (getline(student, line))
{
if (counter % 4 == 2) { // Here I want to delete all prcedent 3 lines}
}
Unfortunately your approach, or your design idea is not that good. It should be improved a little bit.
You need to start thinking in an object-oriented way. The object "student" has properties or data elements, like "name", "id", "age" and "number". All this data belong together and are related to a specific "student". And next, you need to define methods that should operate on this data, like "reading" and "writing" the complete data for some student.
If the "number" property that you have, after reading a complete "student" record, is ok, then you will store it in some kind of array or std::vector
So, lets start with the data part:
struct Student {
std::string name{};
unsigned long id{};
unsigned int age{};
unsigned int number{};
};
This will describe the properties of one student.
Next, we do want to operate on this data. And the first operation is that we do want to read this data from any kind of stream, for example from the console (std::cin) or from a file (std::ifstream). This basically doesn't matter, stream is stream :-)
The iostream library has the so called "extractor" operator for reading from streams. That is the >> operator. And this we can simply define for our "Student" struct. Like so:
friend std::istream& operator >> (std::istream& is, Student& s) {
return std::getline(is >> std::ws, s.name) >> s.id >> s.age >> s.number;
}
The signature of the function is given. This is a formal requirement. Then, now to what are we doing in that function. I would like to remind everybody that the istream extraction operations always return a reference to the given istream.. So, first we call std::getline. This will read the name of the student, and then return again is. Then the line looks like return is >> s.id >> s.age >> s.number. Next we will read the id. This will also return is. Then the statement would look like return is >> s.age >> s.number. And so on and so on. At the end we will have read everything and simply return is;
In the std::getline you will see >>std::ws. This will ignore all leading white spaces like new lines or whatever.
So, now, if we have an open file stream, let' s say "ifs" and a Student variable called "student", then we can write ifs >> student and it will read a complete student record from the file. So, all 4 lines.
By the way, we can do the same mechanism for the inserter function <<.
If we run this in a loop now and store the read student data into a std::vector we can copy all data from the file.
Or, if a student has a certain property, then we can also decide to not copy the data.
This could then look like:
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
struct Student {
std::string name{};
unsigned long id{};
unsigned int age{};
unsigned int number{};
friend std::istream& operator >> (std::istream& is, Student& s) {
return std::getline(is >> std::ws, s.name) >> s.id >> s.age >> s.number;
}
friend std::ostream& operator << (std::ostream& os, const Student& s) {
return os << s.name << '\n' << s.id << '\n' << s.age << '\n' << s.number << '\n';
}
};
int main() {
// Here we will store all our studends
std::vector<Student> students{};
// Open the file with the data
std::ifstream ifs("r:\\data.txt");
// Check, if the file could be opened.
if (ifs) {
Student tempStudent{};
// Read all students from a file, until we hit eof or some other problem
while (ifs >> tempStudent) {
// if the student does fullfill the requirements, then we take it, else not
if (tempStudent.number != 2)
students.emplace_back(std::move(tempStudent));
}
// OK, for debug purposes we show all data ion the screen
for (const Student& s : students) std::cout << s << '\n';
}
else std::cerr << "\n\nError: source file could not be opened\n\n";
return 0;
}
And if you want to see a more advance C++ solution, please look at the below:
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <iterator>
#include <algorithm>
struct Student {
std::string name{};
unsigned long id{};
unsigned int age{};
unsigned int number{};
friend std::istream& operator >> (std::istream& is, Student& s) {
return std::getline(is >> std::ws, s.name) >> s.id >> s.age >> s.number;
}
friend std::ostream& operator << (std::ostream& os, const Student& s) {
return os << s.name << '\n' << s.id << '\n' << s.age << '\n' << s.number << '\n';
}
};
int main() {
// Here we will store all our studends
std::vector<Student> students{};
// Open the file with the data and check, if it is open
if (std::ifstream ifs("r:\\data.txt");ifs) {
// Read all data
std::copy_if(std::istream_iterator<Student>(ifs), {}, std::back_inserter(students), [](const Student& s) {return s.number != 2;});
// For debug purposes we show all data ion the screen
std::copy(students.begin(), students.end(), std::ostream_iterator<Student>(std::cout, "\n"));
}
else std::cerr << "\n\nError: source file could not be opened\n\n";
return 0;
}
I want to read a csv data to vector of struct in cpp, This is what I wrote, I want to store the iris dataset in pointer of struct vector csv std::vector<Csv> *csv = new std::vector<Csv>;
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
struct Csv{
float a;
float b;
float c;
float d;
std::string e;
};
int main(){
std::string colname;
// Iris csv dataset downloaded from
// https://gist.github.com/curran/a08a1080b88344b0c8a7
std::ifstream *myFile = new std::ifstream("iris.csv");
std::vector<Csv> *csv = new std::vector<Csv>;
std::string line;
// Read the column names
if(myFile->good())
{
// Extract the first line in the file
std::getline(*myFile, line);
// Create a stringstream from line
std::stringstream ss(line);
// Extract each column name
while(std::getline(ss, colname, ',')){
std::cout<<colname<<std::endl;
}
}
// Read data, line by line
while(std::getline(*myFile, line))
{
// Create a stringstream of the current line
std::stringstream ss(line);
}
return 0;
}
I dont know how to implement this part of the code which outputs line with both float and string.
// Read data, line by line
while(std::getline(*myFile, line))
{
// Create a stringstream of the current line
std::stringstream ss(line);
}
Evolution
We start with you program and complete it with your current programm style. Then we analyze your code and refactor it to a more C++ style solution. In the end we show a modern C++ solution using more OO methods.
First your completed code:
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
struct Csv {
float a;
float b;
float c;
float d;
std::string e;
};
int main() {
std::string colname;
// Iris csv dataset downloaded from
// https://gist.github.com/curran/a08a1080b88344b0c8a7
std::ifstream* myFile = new std::ifstream("r:\\iris.csv");
std::vector<Csv>* csv = new std::vector<Csv>;
std::string line;
// Read the column names
if (myFile->good())
{
// Extract the first line in the file
std::getline(*myFile, line);
// Create a stringstream from line
std::stringstream ss(line);
// Extract each column name
while (std::getline(ss, colname, ',')) {
std::cout << colname << std::endl;
}
}
// Read data, line by line
while (std::getline(*myFile, line))
{
// Create a stringstream of the current line
std::stringstream ss(line);
// Extract each column
std::string column;
std::vector<std::string> columns{};
while (std::getline(ss, column, ',')) {
columns.push_back(column);
}
// Convert
Csv csvTemp{};
csvTemp.a = std::stod(columns[0]);
csvTemp.b = std::stod(columns[1]);
csvTemp.c = std::stod(columns[2]);
csvTemp.d = std::stod(columns[3]);
csvTemp.e = columns[4];
// STore new row data
csv->push_back(csvTemp);
}
// Show everything
for (const Csv& row : *csv)
std::cout << row.a << '\t' << row.b << '\t' << row.c << '\t' << row.d << '\t' << row.e << '\n';
return 0;
}
The question that you have regarding the reading of the columns from your Csv file, can be answered like that:
You need a temporary vector. Then you use the std::getline function, to split the data in the std::istringstream and to copy the resulting substrings into the vector. After that, we use string conversion functions and assign the rsults in a temporary Csv struct variable. After all conversions have been done, we move the temporary into the resulting csv vector that holds all row data.
Analysis of the program.
First, and most important, in C++ we do not use raw pointers for owned memory. We should ven not use new in most case. If at all, std::unique_ptrand std::make_unique should be used.
But we do not need dynamic memory allocation on the heap at all. You can simply define the std::vector on the functions stack. Same like in your line std::string colname; you can also define the std::vector and the std::ifstream as a normal local variable. Like for example std::vector<Csv> csv{};. Only, if you pass this variable to another function, then use pointers, but smart pointers.
Next, if you open a file, like in std::ifstream myFile("r:\\iris.csv"); you do not need to test the file streams condition with if (myFile->good()). The std::fstreams bool operator is overwritten, to give you exactly this information. Please see here.
Now, next and most important.
The structure of your source file is well known. There is a header with 5 elements and then 4 doubles and at then end a string without spaces. This makes life very easy.
If we would need to validate the input or if there would be spaces within an string, then we would need to implement other methods. But with this structure, we can use the build in iostream facilities. The snippet
// Read all data
Csv tmp{};
char comma;
while (myFile >> tmp.a >> comma >> tmp.b >> comma >> tmp.c >> comma >> tmp.d >> comma >> tmp.e)
csv.push_back(std::move(tmp));
will do the trick. Very simple.
So, the refactored solution could look like this:
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
struct Csv {
float a;
float b;
float c;
float d;
std::string e;
};
int main() {
std::vector<Csv> csv{};
std::ifstream myFile("r:\\iris.csv");
if (myFile) {
if (std::string header{}; std::getline(myFile, header)) std::cout << header << '\n';
// Read all data
Csv tmp{};
char comma;
while (myFile >> tmp.a >> comma >> tmp.b >> comma >> tmp.c >> comma >> tmp.d >> comma >> tmp.e)
csv.push_back(std::move(tmp));
// Show everything
for (const Csv& row : csv)
std::cout << row.a << '\t' << row.b << '\t' << row.c << '\t' << row.d << '\t' << row.e << '\n';
}
return 0;
}
This is already much more compact. But there is more . . .
In the next step, we want to add a more Object Oriented approch.
The key is that data and methods, operating on this data, should be encapsulated in an Object / class / struct. Only the Csv struct should know, how to read and write its data.
Hence, we overwrite the extractor and inserter operator for the Csv struct. We use the same approach than before. We just encapsulate the reading and writing in the struct Csv.
After that, the main function will be even more compact and the usage is more logical.
Now we have:
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
struct Csv {
float a;
float b;
float c;
float d;
std::string e;
friend std::istream& operator >> (std::istream& is, Csv& c) {
char comma;
return is >> c.a >> comma >> c.b >> comma >> c.c >> comma >> c.d >> comma >> c.e;
}
friend std::ostream& operator << (std::ostream& os, const Csv& c) {
return os << c.a << '\t' << c.b << '\t' << c.c << '\t' << c.d << '\t' << c.e << '\n';
}
};
int main() {
std::vector<Csv> csv{};
if (std::ifstream myFileStream("r:\\iris.csv"); myFileStream) {
if (std::string header{}; std::getline(myFileStream, header)) std::cout << header << '\n';
// Read all data
Csv tmp{};
while (myFileStream >> tmp)
csv.push_back(std::move(tmp));
// Show everything
for (const Csv& row : csv)
std::cout << row;
}
return 0;
}
OK. Alread rather good. Bit there is even more possible.
We can see that the source data has a header and then Csv data.
Also this can be modelled into a struct. We call it Iris. And we also add an extractor and inserter overwrite to encapsulate all IO-operations.
Additionally we use now modern algorithms, regex, and IO-iterators. I am not sure, if this is too complex now. If you are interested, then I can give you further information. But for now, I will just show you the code.
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <regex>
#include <iterator>
const std::regex re{ "," };
struct Csv {
float a;
float b;
float c;
float d;
std::string e;
// Overwrite extratcor for simple reading of data
friend std::istream& operator >> (std::istream& is, Csv& c) {
char comma;
return is >> c.a >> comma >> c.b >> comma >> c.c >> comma >> c.d >> comma >> c.e;
}
// Ultra simple inserter
friend std::ostream& operator << (std::ostream& os, const Csv& c) {
return os << c.a << "\t\t" << c.b << "\t\t" << c.c << "\t\t" << c.d << "\t\t" << c.e << '\n';
}
};
struct Iris {
// Iris data consits of header and then Csv Data
std::vector<std::string> header{};
std::vector<Csv> csv{};
// Overwrite extractor for generic reading from streams
friend std::istream& operator >> (std::istream& is, Iris& i) {
// First read header values;
if (std::string line{}; std::getline(is, line))
std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {}, std::back_inserter(i.header));
// Read all csv data
std::copy(std::istream_iterator<Csv>(is), {}, std::back_inserter(i.csv));
return is;
}
// Simple output. Copy data to stream os
friend std::ostream& operator << (std::ostream& os, const Iris& i) {
std::copy(i.header.begin(), i.header.end(), std::ostream_iterator<std::string>(os, "\t")); std::cout << '\n';
std::copy(i.csv.begin(), i.csv.end(), std::ostream_iterator<Csv>(os));
return os;
}
};
// Driver Code
int main() {
if (std::ifstream myFileStream("r:\\iris.csv"); myFileStream) {
Iris iris{};
// Read all data
myFileStream >> iris;
// SHow result
std::cout << iris;
}
return 0;
}
Look at the main function and how easy it is.
If you have questions, then please ask.
Language: C++17
Compiled and tested with MS Visual Studio 2019, community edition
I'm a beginner in programming and i'm trying to read my .txt file into an array of struct in this program which after that display the data and then sort it, but the program only reads the first line and the loop won't stop until arraysize.
The file data looks like this:
ID NAME ADDRESS AGE
The Code:
#include <iostream>
#include <fstream>
#include <string>
#include <conio.h>
using namespace std;
struct bio
{
char name[50], address[50];
int id, age;
};
int main()
{
int i = 0, arraysize = 1000;
bio b[arraysize];
fstream data;
data.open("biodata.txt");
while(data.read((char*)&b, sizeof(b[i])))
{
for (i = 1; i < 1000; i++)
{
data >> b[i].id >> b[i].name >> b[i].address >> b[i].age;
}
}
for (i = 0; i < 1000; i++)
{
cout << b[i].id << " " << b[i].name << " " << b[i].address << " " << b[i].age << " " << endl;
}
data.close();
getch();
}
#include <iostream>
#include <fstream>
#include <string>
#define ARRAY_SIZE 1000
#define FILE_NAME "biodata.txt"
using namespace std;
struct Bio
{
int m_id;
string m_name;
string m_address;
int m_age;
};
int main()
{
Bio bio[ARRAY_SIZE];
ifstream data;
data.open(FILE_NAME);
if (!data)
{
cout << "not file " << FILE_NAME;
return 0;
}
for (int i = 0; i < ARRAY_SIZE && data.good(); ++i)
{
data >> bio[i].m_id >> bio[i].m_name >> bio[i].m_address >> bio[i].m_age;
}
for (int i = 0; i < ARRAY_SIZE ; ++i)
{
cout << bio[i].m_id << " " << bio[i].m_name << " " << bio[i].m_address << " " << bio[i].m_age << " " << endl;
}
data.close();
}
a few comments:
for what conio lib?
struct (bio) start with capital letter
don't use in char array in c++, you have string for this.
separate the variables to separate lines (bot "char name[50], address[50];")
better to rename members to m_X
about your "arraysize". if it const number you decide, do it with #define. if you need the whole file, you don't need it at all. (the file name too)
ifstream and not fstream data. you need just read. you don't want to change your data with some mistake.
check it the file opened well
in your code you check the while just before the loop.
in your condition loop check data.good(). it check it not eof and he file is readable.
read command is for binary file
it's better to separate the load file and print data to 2 differents functions. I didn't do it for save on your template
The following is maybe a little complicated for beginners, but since we are talking about C++, we should look also to a "more" objective oriented approach.
You designed a class, called bio. In object oriented languages you will put all data for an object and also all functions that operate on this data in the class. So you need to add member functions. The idea is that you encapsulate all data in an object. The outside world should not know anything about the details of the class. You just access it via member functions. And if you want to make changes later than you will do this within the member functions of the classes. And the rest of the program will continue to work.
Additionally we should definitely use C++ language features. For examples you should use std::string for strings and not Plain old C-Style char arrays. You should basically never use C-Style arrays in C++. Instead, please use STL container.
So, then let's design a class with data members and member functions. Since at the moment we just need input and output functionality, we overwrite the inserter and extractor operator. These operators know abot the data and behaviour of the class and will take care.
See the following program:
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
#include <sstream>
struct Bio
{
// Data
unsigned int id{};
std::string name{};
std::string address{};
unsigned int age{};
// Overload extractor operator to read all data
friend std::istream& operator >> (std::istream& is, Bio& b) {
std::string textLine{};
if (std::getline(is, textLine)) {
std::istringstream textLineStream{textLine};
textLineStream >> b.id >> b.name >> b.address >> b.age;
}
return is;
}
// Overload inserter operator to print the data
friend std::ostream& operator << (std::ostream& os, const Bio& b) {
return os << b.id << " " << b.name << " " << b.address << " " << b.age;
}
};
std::istringstream sourceFile{R"(1 John Address1 31
2 Paul Address2 32
3 Ringo Address3 33
4 George Address4 34
)"};
int main()
{
// Define Variable and read complete source file
std::vector<Bio> bio{std::istream_iterator<Bio>(sourceFile), std::istream_iterator<Bio>()};
// Sort the guys by name
std::sort(bio.begin(), bio.end(), [](const Bio& b1, const Bio& b2){ return b1.name < b2.name;});
// Show output on screen
std::copy(bio.begin(),bio.end(),std::ostream_iterator<Bio>(std::cout, "\n"));
return 0;
}
Some comments. On StackOverflow, I cannot use files. So in my example program, I use a std::istringstream instead. But this is also an std::istream. You can use any other std::istream as well. So if you define an `````std::ifstreamto read from a file, then it will work in the same way as thestd::istringstream````.
And please see. The extractor operator does the whole work of reading the source File. It is encapsulated. No outside function needs to know, how it does.
In the main function, we define a std::vector and use its range contructor to specifiy where the data comes from. We give it the std::istream_iterator, which iterates over the input data and calls the extractor operator until verything is read.
Then we sort by names and copy the result to the output.
You may notice that fields in your input data are separted by space. This does in general not work for none atomic data. The name could exist of 2 parts and the address can have a street and a city. For this CSV (Comma separated Values) files have been invented.
Please see a more realistic soultion below.
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
#include <sstream>
#include <regex>
struct Bio
{
// Data
unsigned int id{};
std::string name{};
std::string address{};
unsigned int age{};
// Overload extractor operator to read all data
friend std::istream& operator >> (std::istream& is, Bio& b) {
std::string line{};
std::regex re{";"};
if (std::getline(is, line)) {
std::vector<std::string> token{std::sregex_token_iterator(line.begin(), line.end(), re, -1), std::sregex_token_iterator()};
if (4 == token.size()) {
b.id = std::stoul(token[0]);
b.name = token[1];
b.address = token[2];
b.age = std::stoul(token[3]);
}
}
return is;
}
// Overload inserter operator to print the data
friend std::ostream& operator << (std::ostream& os, const Bio& b) {
return os << b.id << ", " << b.name << ", " << b.address << ", " << b.age;
}
};
std::istringstream sourceFile{R"(1; John Lenon; Street1 City1; 31
2; Paul McCartney; Street2 City2; 32
3; Ringo Starr; Street3 City3; 33
4; George Harrison; Address4; 34
)"};
int main()
{
// Define Variable and read complete source file
std::vector<Bio> bio{std::istream_iterator<Bio>(sourceFile), std::istream_iterator<Bio>()};
// Sort the guys by name
std::sort(bio.begin(), bio.end(), [](const Bio& b1, const Bio& b2){ return b1.name < b2.name;});
// Show output on screen
std::copy(bio.begin(),bio.end(),std::ostream_iterator<Bio>(std::cout, "\n"));
return 0;
}
We have a new source format and main is unchanged. Just the extractor operator is modified. Here we are using a different iterator to get the source data.
So I'm trying to read input like this from the standard input (using cin):
Adam English 85
Charlie Math 76
Erica History 82
Richard Science 90
My goal is to eventually store each data piece in its own cell in a data structure I have created, so basically I want to parse the input so each piece of data is individual. Since each row of input is inputted by the user one at a time, each time I get an entire row of input that I need to parse. Currently I am trying something like this:
stringstream ss;
getline(cin, ss);
string name;
string course;
string grade;
ss >> name >> course >> grade;
The error I am having is that XCode is telling me there's no matching function call to getline which is confusing me. I have included the string library, so I'm guessing the error has to do with using getline to read in from cin to a stringstream? Any help here would be appreciated.
You are almost there, the error is most probably1 caused because you are trying to call getline with second parameter stringstream, just make a slight modification and store the data within the std::cin in a string first and then used it to initialize a stringstream, from which you can extract the input:
// read input
string input;
getline(cin, input);
// initialize string stream
stringstream ss(input);
// extract input
string name;
string course;
string grade;
ss >> name >> course >> grade;
1. Assuming you have included:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
You cannot std::getline() a std::stringstream; only a std::string. Read as a string, then use a stringstream to parse it.
struct Student
{
string name;
string course;
unsigned grade;
};
vector <Student> students;
string s;
while (getline( cin, s ))
{
istringstream ss(s);
Student student;
if (ss >> student.name >> student.course >> student.grade)
students.emplace_back( student );
}
Hope this helps.
You can just use cin >> name >> course >> grade; because >> will read until whitespace anyway.
Either you don't have a using namespace std in your code or you're not fully qualifying calls made to the API's in the std namespace with an std:: prefix, for example, std::getline(). The solution below parses CSV instead to tokenize values that have whitespace in them. The logic for stdin extraction, parsing the CSV, and converting grade from string to int are all separated. The regex_token_iterator usage is probably the most complicated part, but it uses pretty simple regex for the most part.
// foo.txt:
// Adam,English,85
// Charlie,Math,76
// Erica,History,82
// Richard,Science,90
// John,Foo Science,89
// after compiling to a.exe, run with:
// $ ./a.exe < foo.txt
// output
// name: Adam, course: English, grade: 85
// name: Charlie, course: Math, grade: 76
// name: Erica, course: History, grade: 82
// name: Richard, course: Science, grade: 90
// name: John, course: Foo Science, grade: 89
#include <iostream>
#include <sstream>
#include <regex>
#include <vector>
using namespace std;
typedef unsigned int uint;
uint stoui(const string &v) {
uint i;
stringstream ss;
ss << v;
ss >> i;
return i;
}
string strip(const string &s) {
regex strip_pat("^\\s*(.*?)\\s*$");
return regex_replace(s, strip_pat, "$1");
}
vector<string> parse_csv(string &line) {
vector<string> values;
regex csv_pat(",");
regex_token_iterator<string::iterator> end;
regex_token_iterator<string::iterator> itr(
line.begin(), line.end(), csv_pat, -1);
while (itr != end)
values.push_back(strip(*itr++));
return values;
}
struct Student {
string name;
string course;
uint grade;
Student(vector<string> &data) :
name(data[0]), course(data[1]), grade(stoui(data[2])) {}
void dump_info() {
cout << "name: " << name <<
", course: " << course <<
", grade: " << grade << endl;
}
};
int main() {
string line;
while (getline(cin, line)) {
if (!line.empty()) {
auto csv = parse_csv(line);
Student s(csv);
s.dump_info();
}
}
}