How to set class objects from vector of string in c++ - c++

These are the data in my Login.csv file:
ID,Name,Password,Gender
1,Liam,1234,M
2,Janice,0000,F
So probably I'll use class & objects to create login details, and write it into the file. After that I will split the csv from file into a vector of strings, from there how do I load back the details to objects of class.
This is my code of splitting the csv from file:
int _tmain(int argc, _TCHAR* argv[])
{
string line;
ifstream fin("users.csv");
while (getline(fin, line)){
vector<string> token;
split(line, ',', token);
for (int i = 0; i < token.size(); i++){
cout << token[i] << " ";
//////////// <<here>>
}
cout << endl;
}
system("pause");
return 0;
}
void split(const string& s, char c, vector<string>& v) {
string::size_type i = 0;
string::size_type j = s.find(c);
while (j != string::npos) {
v.push_back(s.substr(i, j - i));
i = ++j;
j = s.find(c, j);
if (j == string::npos)
v.push_back(s.substr(i, s.length()));
}
}
I was thinking how can I set the splitted strings from the string vector to a vector of objects, something like this: (to put in the << here >> section i commented in above)
vector<Login>loginVector;
//all the objects below should set from string vector (token)
loginVector[i].setID(); //i=0, id=1, name=Liam, password=1234, gender=M
loginVector[i].setName();
loginVector[i].setPassword();
loginVector[i].setGender();
loginVector[i].setID(); //i=1, id=2, name=Janice, password=0000, gender=M
loginVector[i].setName();
loginVector[i].setPassword();
loginVector[i].setGender();
Thank you.

Implement your Login object and populate it in the loop.
struct Login {
enum Gender {
Male,
Female
};
int Id;
std::string Name;
std::string Password;
/* you should probably use a constructor */
};
/* to construct your vector */
int I = 0;
while(I < token.size()) {
/* in your iterator */
Login& LO = loginVector.emplace_back(Login{});
LO.Id = std::stoi(token[++I]);
LO.Name = token[++I];
/* etc...*/
}
Note that this assumes your CSV is well formed, up to you to implement all the checking and make sure you handle corner cases like possible errors from stoi, blank rows or missing columns.
Also, don't do system("pause");, you're executing a shell to sleep for you, which has massive overhead compared to just using sleep which does literally the same thing except in a far more direct way.

I personally would implement this by adding an extraction operator for your class.
You'll have to friend the extraction operator because it must be defined externally to your class, since it's actually operating on the istream and not on your class, so something like this should be defined in your class:
friend istream& operator>> (istream& lhs, Login& rhs);
Depending on how your variables are named your extraction operator should look something like this:
istream& operator>> (istream& lhs, Login& rhs) {
char gender;
lhs >> rhs.id;
lhs.ignore(numeric_limits<streamsize>::max(), ',');
getline(lhs, rhs.name, ',');
getline(lhs, rhs.password, ',');
lhs >> ws;
lhs.get(gender);
rhs.isMale = gender == 'm' || gender == 'M';
return lhs;
}
Live Example

Related

storing input from text file into 2d array using vectors

So, I need to store the data from the text file into 2d array. I tried using vectors. So here is the sample data from the text file:
START 13
PID 11
CORE 120
SSD 0
CORE 60
SSD 0
CORE 20
SSD 0
I want to store this data as final_vec[x][y]. This is what I tried:
void read_file(const string &fname) {
ifstream in_file(fname);
string line;
vector<string> temp_vec;
vector<vector<string>> final_vec;
while ( getline (in_file,line) )
{
stringstream ss(line);
string value;
while(ss >> value)
{
temp_vec.push_back(value);
}
final_vec.push_back(temp_vec);
}
for (int i = 0; i < final_vec.size(); i++) {
for (int j = 0; j < final_vec[i].size(); j++)
cout << final_vec[i][j] << " ";
cout << endl;
}
}
int main()
{
read_file("test.txt");
return 0;
}
I get error:
main.cpp: In function ‘void read_file(const string&)’:
main.cpp:29:29: error: variable ‘std::stringstream ss’ has initializer but incomplete type
stringstream ss(line);
I am not sure if I am on the right track.
IMHO, a better solution is to model each line as a record, with a struct or class:
struct Record
{
std::string label;
int number;
friend std::istream& operator>>(std::istream& input, Record& r);
};
std::istream& operator>>(std::istream& input, Record& r)
{
input >> r.label;
input >> r.number;
return input;
}
The overloaded operator>> makes the input loop a lot simpler:
std::vector<Record> database;
Record r;
while (infile >> r)
{
database.push_back(r);
}
Rather than have a 2d vector of two different types, the above code uses a 1D vector of structures.

How do I sort string vectors based on 2nd column/3rd column etc?

I have a
vector<string>data
organized as such
//NAME ID AGE
//NAME ID AGE
//NAME ID AGE
//NAME ID AGE
I can sort it by name alphabetically, how can I sort it in ascending order based on the 2nd column/3rd column instead? Thank you for any assistance and advice.
std::sort's third overload has a third parameter allows you to provide a function to do the ordering logic.
// get nth token from a string
std::string getnTh(const std::string & str, int n)
{
std::istringstream strm(str);
std::string result;
for (int count = 0; count < n; count++)
{
if (!(strm >> result))
{
throw std::out_of_range("ran out of tokens before n");
}
}
return result;
}
// get ID, second token, from string
std::string get_ID(const std::string str)
{
return getnTh(str, 2);
}
// compare the ID field, second token, in two strings
bool cmp_ID(const std::string &a, const std::string &b)
{
std::string tokena = get_ID(a);
std::string tokenb = get_ID(b);
return tokena < tokenb;
}
int main()
{
std::vector<std::string> data {"c c c ", "b b b " , "a a a"};
std::sort (data.begin(), data.end(), cmp_ID);
}
Note: This code could be crunched down a bit. I've broken it down step by step for easy reading.
Note: This is BRUTAL! It is constantly parsing the same strings over and over, a disgusting waste of effort.
Instead you should make a structure to store the already parsed string and store that structure in the std::vector.
// stores a person
struct person
{
std::string name;
std::string ID;
std::string age;
// constructor to parse an input string into a new person
person(const std::string & in)
{
std::istringstream strm(in);
if (!(strm >> name >> ID >> age))
{
throw std::runtime_error("invalid person input");
}
}
};
// much simpler and faster compare function. All of the parsing is done once ahead of time.
bool cmp_ID(const person &a, const person &b)
{
return a.ID < b.ID;
}
int main()
{
// replaces vector<string> data
std::vector<person> data {{"c c c"}, {"b b b"} , {"a a a"}};
std::sort (data.begin(), data.end(), cmp_ID);
}
You can read those Strings by each character until you hit the first/second space.
Then you should be able to "filter" out the first/second attribute.

How to read pieces of string into a class array C++

I have an array of dvd from a Video class I created
Video dvd[10];
each video has the property,
class Video {
string _title;
string _genre;
int _available;
int _holds;
public:
Video(string title, string genre, int available, int holds);
Video();
void print();
void read(istream & is, Video dvd);
int holds();
void restock(int num);
string getTitle();
~Video();
};
I'm trying to fill up this array with data from my text file where each info such as the title and genre is separated by a comma
Legend of the seeker, Fantasy/Adventure, 3, 2
Mindy Project, Comedy, 10, 3
Orange is the new black, Drama/Comedy, 10, 9
I've tried using getline(in, line, ',') but my brain halts when its time to insert each line into the dvd array.
I also created a read method to read each word separated by a whitespace but I figured thats not what I really want.
I also tried to read a line with getline, store the line in a string and split it from there but I get confused along the line.
**I can get the strings I need from each line, my confusion is in how to insert it into my class array in the while loop especially when I can only read one word at a time.
I need help on what approach I should follow to tackle this problem.
**My code
#include <iostream>
#include <fstream>
#include <cassert>
#include <vector>
#define MAX 10
using namespace std;
class Video {
string _title;
string _genre;
int _available;
int _holds;
public:
Video(string title, string genre, int available, int holds);
Video();
void print();
void read(istream & is, Video dvd);
int holds();
void restock(int num);
string getTitle();
~Video();
};
Video::Video(string title, string genre, int available, int holds){
_title = title;
_genre = genre;
_available = available;
_holds = holds;
}
void Video::read (istream & is, Video dvd)
{
is >> _title >> _genre >> _available>>_holds;
dvd = Video(_title,_genre,_available,_holds);
}
int Video::holds(){
return _holds;
}
void Video::restock(int num){
_available += 5;
}
string Video::getTitle(){
return _title;
}
Video::Video(){
}
void Video::print(){
cout<<"Video title: " <<_title<<"\n"<<
"Genre: "<<_genre<<"\n"<<
"Available: " <<_available<<"\n"<<
"Holds: " <<_holds<<endl;
}
Video::~Video(){
cout<<"DESTRUCTOR ACTIVATED"<<endl;
}
int main(int params, char **argv){
string line;
int index = 0;
vector<string> tokens;
//Video dvd = Video("23 Jump Street", "comedy", 10, 3);
//dvd.print();
Video dvd[MAX];
dvd[0].holds();
ifstream in("input.txt");
/*while (getline(in, line, ',')) {
tokens.push_back(line);
}
for (int i = 0; i < 40; ++i)
{
cout<<tokens[i]<<endl;
}*/
if(!in.fail()){
while (getline(in, line)) {
dvd[index].read(in, dvd[index]);
/*cout<<line<<endl;
token = line;
while (getline(line, token, ',')){
}
cout<<"LINE CUT#####"<<endl;
cout<<line<<endl;
cout<<"TOKEN CUT#####"<<endl;*/
//dvd[index] =
index++;
}
}else{
cout<<"Invalid file"<<endl;
}
for (int i = 0; i < MAX; ++i)
{
dvd[i].print();
}
}
First, I would change the Video::read function into an overload of operator >>. This will allow the Video class to be used as simply as any other type when an input stream is being used.
Also, the way you implemented read as a non-static member function returning a void is not intuitive and very clunky to use. How would you write the loop, and at the same time detect that you've reached the end of file (imagine if there are only 3 items to read -- how would you know to not try to read a fourth item)? The better, intuitive, and frankly, de-facto way to do this in C++ is to overload the >> operator.
(At the end, I show how to write a read function that uses the overloaded >>)
class Video
{
//...
public:
friend std::istream& operator >> (std::istream& is, Video& vid);
//..
};
I won't go over why this should be a friend function, as that can be easily researched here on how to overload >>.
So we need to implement this function. Here is an implementation that reads in a single line, and copies the information to the passed-in vid:
std::istream& operator >> (std::istream& is, Video& vid)
{
std::string line;
std::string theTitle, theGenre, theAvail, theHolds;
// First, we read the entire line
if (std::getline(is, line))
{
// Now we copy the line into a string stream and break
// down the individual items
std::istringstream iss(line);
// first item is the title, genre, available, and holds
std::getline(iss, theTitle, ',');
std::getline(iss, theGenre, ',');
std::getline(iss, theAvail, ',');
std::getline(iss, theHolds, ',');
// now we can create a Video and copy it to vid
vid = Video(theTitle, theGenre,
std::stoi(theAvail), // need to change to integer
std::stoi(theHolds)); // same here
}
return is; // return the input stream
}
Note how vid is a reference parameter, not passed by value. Your read function, if you were to keep it, would need to make the same change.
What we did above is that we read the entire line in first using the "outer" call to std::getline. Once we have the line as a string, we break down that string by using an std::istringstream and delimiting each item on the comma using an "inner" set of getline calls that works on the istringstream. Then we simply create a temporary Video from the information we retrieved from the istringstream and copy it to vid.
Here is a main function that now reads into a maximum of 10 items:
int main()
{
Video dvd[10];
int i = 0;
while (i < 10 && std::cin >> dvd[i])
{
dvd[i].print();
++i;
}
}
So if you look at the loop, all we did is 1) make sure we don't go over 10 items, and 2) just use cin >> dvd[i], which looks just like your everyday usage of >> when inputting an item. This is the magic of the overloaded >> for Video.
Here is a live example, using your data.
If you plan to keep the read function, then it would be easier if you changed the return type to bool that returns true if the item was read or false otherwise, and just calls the operator >>.
Here is an example:
bool Video::read(std::istream & is, Video& dvd)
{
if (is.good())
{
is >> dvd;
return true;
}
return false;
}
And here is the main function:
int main()
{
Video dvd[10];
int i = 0;
while (i < 10 && dvd[i].read(std::cin, dvd[i]))
{
dvd[i].print();
++i;
}
}
Live Example #2
However, I still say that the making of Video::read a non-static member makes the code in main clunky.

Storing lines of input into a vector

In my program I am trying to take from the user lines of input actually names then storing them into a vector.
I wrote my own code but I got a runtime error telling me that "string subscript out of range".
This is my code
const int LEN = 100;
struct Case{
public:
int No_People;
vector<string> Names;
vector<string> Results;
void Set_Data(){
cin >> No_People;
int Size = No_People;
char Line[LEN];
for (int i = 0; i < Size; i++){
cin.getline(Line, LEN);
Names.push_back(Line);
}
}
}
Personally I would define a class to represent a line. Then you can use stream iterators to load the vector.
class Line
{
std::string line;
public:
// Operator to convert a line back to a std::string
operator std::string const&() const {return line;}
// Friend function to read a line from a stream.
friend std::istream& operator>>(std::istream& in, Line& data)
{
return std::getline(in, data.line);
}
};
int main()
{
int countOfPeople;
std::cin >> countOfPeople;
std::vector<std::string> lines;
std::copy_n((std::istream_iterator<Line>(std::cin)), countOfPeople,
std::back_insert_iterator(lines));
}
There's no need to use a char[] array, use std::string instead, especially given that you already are using it.
Note to OP: cin.getline() is this one:
std::istream::getline(char*, int)
The one you ned to use for std::string's is this one:
std::getline(istream&, string&)
struct Case{
public:
int Size;
vector<string> Names;
vector<string> Results;
void Set_Data(){
std::string temp;
cin >> Size; cin.ignore();
for (int i = 0; i < Size; i++){
std::getline(cin, temp);
Names.push_back(temp);
}
}
}
As far as compile errors go, always:
quote the exact error messgae
tell the line it happened at
show the code that contains the line and the relevant classes/methods
Most probably you are accessing the string using subscript which is out of index. It will be easy to answer if you point at which line you are getting the error.

Parse Comma Delimited File without using <string>

I have a file which looks like:
123,Cheese,Butter
78,Milk,Vegetable,Fish
and I wish to read each line into a data type List which has int num and char things[3][10] using overloaded operator >>. So far I have:
friend istream& operator>> (istream &is, List &rhs)
{
char comma;
is >> rhs.num >> comma >> ... (I don't know how to continue)
return is;
} // operator>>
Am I doing it right using char comma to skip a comma? How do I read different entries with different lengths separated by comma without using string?
It will be only a pseudocode but if you really need to avoid std::string your best choice is to make it more or less look like this:
istream &operator >>(istream &s, YourType &mylist) {
char mybuf[256];
s.read(mybuf, 256);
char *beg = mybuf;
char *cur = beg;
while (cur != mybuf + 256 && *cur!=0) {
if (*cur == '\n') {
mylist.addnext();
}
if (*cur == ',') {
*cur = 0; //to make the char string end on each comma
mylist.current.add(beg);
beg = cur + 1;
}
}
}
Remember that if YourType will be for example vector<vector<const char *>> you will need to add the operator >> into the std namespace.