I need to parse through a text file that contains something like :
1|Song Title|Release date||"ignore me"|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0
which is the song number, followed by the release date, followed by a website that I need to ignore, and followed by a series of 0's and 1's which could represent an vector of genres.
I need a way to separate this data, and ignore the one that say's the website while at the same time creating a new instance of a Song Object which has an : (int songNumber,string songTitle, vector* genres, string releaseDate)
Thanks!
The C++ String Toolkit Library (StrTk) has the following solution to your problem:
#include <string>
#include <deque>
#include "strtk.hpp"
struct song_type
{
unsinged int id;
std::string release_date;
std::string url;
char genre[8];
};
strtk_parse_begin(song_type)
strtk_parse_type(id)
strtk_parse_type(release_date)
strtk_parse_type(url)
strtk_parse_type(genre[0])
strtk_parse_type(genre[1])
strtk_parse_type(genre[2])
strtk_parse_type(genre[3])
strtk_parse_type(genre[4])
strtk_parse_type(genre[5])
strtk_parse_type(genre[6])
strtk_parse_type(genre[7])
strtk_parse_end()
int main()
{
std::deque<song_type> song_list;
strtk::for_each_line("songs.txt",
[&song_list](const std::string& line)
{
song_type s;
if (strtk::parse(line,"|",s))
song_list.push_back(s);
});
return 0;
}
More examples can be found Here
Define a class Song that holds the data in the form you require, as you stated above
implement Song::operator>>(const istream&); to populate the class by parsing the above data from an input stream
read the file line by line using string::getline
for each line, convert to stringstream and then use your operator>> to fill in the fields in an instance of Song.
It's straightforward to tokenize the stringstream with the '|' character as a separator, which would be the bulk of the work.
int main()
{
std::string token;
std::string line("1|Song Title|Release date||\"ignore me\"|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0");
std::istringstream iss(line);
while ( getline(iss, token, '|') )
{
std::cout << token << std::endl;
}
return 0;
}
Code lifted from here.
You'd typically do this by overloading operator>> for the type of object:
struct song_data {
std::string number;
std::string title;
std::string release_date;
// ...
};
std::istream &operator>>(std::istream &is, song_data &s_d) {
std::getline(is, s_d.number, '|');
std::getline(is, s_d.title, '|');
std::getline(is, s_d.release_date, '|');
std::string ignore;
std::getline(is, ignore, '|');
// ...
return is;
}
Depending on whether there are more fields you might want to ignore (especially trailing fields) it can sometimes be more convenient to read the entire line into a string, then put that into an istringstream, and parse the individual fields from there. In particular, this can avoid extra work reading more fields you don't care about, instead just going on to the next line when you've parsed out the fields you care about.
Edit: I would probably handle the genres by adding a std::vector<bool> genres;, and reading the 0's and 1's into that vector. I'd then add an enumeration specifying what genre is denoted by a particular position in the vector, so (for example) testing whether a particular song is classified as "country" would look something like:
enum { jazz, country, hiphop, classic_rock, progressive_rock, metal /*, ... */};
if (songs[i].genres[country])
if (songs[i].genres[hiphop])
process_hiphop(songs[i]);
Of course, the exact genres and their order is something I don't know, so I just made up a few possibilities -- you'll (obviously) have to use the genres (and order) defined for the file format.
As far as dealing with hundreds of songs goes, the usual way would be (as implied above) create something like: std::vector<song_data> songs;. Using a stream extraction like above, you can then copy the data from the file to the vector:
std::copy(std::istream_iterator<song_data>(infile),
std::istream_iterator<song_data>(),
std::back_inserter(songs));
If you're likely to look up songs primarily by name (for one example), you might prefer to use std::map<std::string, song_data> songs. This will make it easy to do something like:
songs["new song"].release_date = Today;
Related
I have to implement polymorphism in my project. I have a virtual class called "Account". Then there are 4 subclasses: USD, EUR, GBP, and CHF.
I need to read the current balance from a text file like this:
USD 50
CHF 80
GBP 10
EUR 90
and make a subclass depending on the currency.
Every currency should have its own object. Later in the program, I will implement currency exchange, exchange rates will be read from the file. I don't have any idea of how to start with these classes. What should I learn?
My code so far:
class Account{
std::string currency;
public:
virtual void balance() = 0;
};
class currencyAcc: public Konto {
std::string currency;
float balance;
walutowe(std::string currency,float balance) {
this->currency= currency;
this->balance= balance;
}
void AccBallance() {
std::cout << balance<< std::endl;
}
};
What should I learn?
Well, if you have covered the basics, you sure need some practice and guidance!
You could have a global function that:
reads a block of text file,
parses and creates the correct object dynamically (based on some condition), and
returns a pointer to the object (cast to the base):
Account * parseOne(std::fstream& file); // Reference to opened file
Even if you just want the code, you will still have to go through an explanation. :)
Let us see it in a general sense.
Read a line
Very simply:
std::getline(file, line);
it. You should also check if the read was successful.
Parse it
You can do this as:
std::stringstream parse_me(line);
parse_me >> some_data_1;
parse_me >> some_data_2;
...
Create your object...
Here, you need to create it on the basis of currency_type. Do:
if(currency_type == "GBP")
{
new_currency_object = new GBP(balance);
}
for each derived class.
...and The Code:
Putting it together:
Account * parseOne(std::fstream& file) // Reference to opened file
{
// To read a line from the file
std::string line;
// If the read failed, return NULL
if(!std::getline(file, line))
{
return 0;
}
// Read success
// Using stringstream so that different types of data can be parsed
std::stringstream line_buffer(line);
// Declare variables to be parsed
std::string currency_type;
float balance;
// Now parse it (note the order!)
line_buffer >> currency_type >> balance;
// Create a pointer to base...
Account * new_currency_object;
// ...and create the object based on the currency_type
if(currency_type == "USD")
{
new_currency_object = new USD(balance);
}
... for each currency
// We are done, return the fruits of our labour
return new_currency_object;
}
(Note that I assume you have a USD(float balance) constructor. If not, set balance yourself)
to be used as:
// open the file
std::fstream currency_file("my_currencies.txt");
// Read a currency
Account * a_currency;
// When the read finishes, we get NULL
while(a_currency = parseOne(currency_file))
{
// do something with a_currency. Maybe:
// list_of_currencies.push_back(a_currency) it?
}
Edit: And be sure to deallocate the memory once done! In fact, use of new and raw pointers are not encouraged anymore. Thanks to this comment for suggesting it.
For further reading, see How to implement the factory method pattern in C++ correctly
.
Good luck!
You need a Currency class that accepts the currency code in the constructor. The object of the Currency class can be part of the account via composition, instead of the current currency with datatype string.
I have something like that :
istream ifs("/path/to/my/file.ppm", ios::binary);
So now, for checking the extension file, It's necessary to get the name of the file.
I'm using my own function read :
... readPPM(std::istream& is) {}
It's is possible to get the /path/to/my/file.ppm in a string from the istream& variable ?
You almost certainly actually used
std::ifstream ifs(...);
// ^
However, even so the stream doesn't retain the name used to open it: there is rarely a need to doing so and it would be a wasted resource for most applications. That is, if you need the name later, you'll need to retain it. Also, not all streams have a name. For example, an std::istringstream doesn't have a name.
If you can't pass the stream's name separate from the stream, you can attach the name, e.g., using the pword() member:
int name_index() {
static int rc = std::ios_base::xalloc(); // get an index to be used for the name
return rc;
}
// ...
std::string name("/path/to/my/file.ppm");
std::ifstream ifs(name, ios::binary);
ifs.pword(name_index()) = const_cast<char*>(name.c_str());
// ...
char const* stream_name = static_cast<char*>(ifs.pword(name_index()));
The stream won't maintain the pointer in any shape or form, i.e., with the above setup the name needs to outlive the ifs object. If necessary the objects stored with pword() can be maintained using the various callbacks but doing so is non-trivial.
I am creating a bank terminal for an assignment. It has the ability to add clients with each client containing 5 different variables for name, address, social#, employer, and income. Those variables are then written to a file once they have been filled and the terminal is exited.
The problem I am having is when starting the terminal I need to read these values from the file, each on their separate lines and store them in their respective variables to use in the addClient() function. This is the code snippet to make things easier than submitted my entire project:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
using namespace std;
std::ifstream infile2("client-info.txt");
//Strings used for respective items from file
string clientName, clientAddress, clientSocial, clientEmployer, clientIncome;
//Here is where I am having the problem of reading the info from the file
//line by line and storing it in respective variables.
while (infile2)
{
getline(infile2,clientName);
getline(infile2,clientAddress);
getline(infile2,clientSocial);
getline(infile2,clientEmployer);
getline(infile2,clientIncome);
client.addClient(clientName, clientAddress, clientSocial, clientEmployer, clientIncome);
}
infile2.close();
}
The file, for example is stored as such.
John Doe
123 Easy Lane
123-45-6789
USSRC
36000
The problem I am having is that I cannot figure out a solid way to get each line and store them in their respective strings. For the assignment, I will not have to be dealing with blank spaces and such. So lines 0-4 will be for one client, 5-9 for another, etc.
A push in the right direction would be greatly appreciated, thanks!
If the addClient function accepts 5 parameters as you have did, then your current main function has already solved your questions.
If you would like to put those 5 strings into a single string, then work this single string inside addClient function.
You can create a class:
class ClientInfo
{
private:
string clientName;
string clientAddress;
string clientSocial;
string clientEmployer,;
string clientIncome;
public:
ClientInfo(string name, string addr, string ssn,
string employer, string income):
clientName(name), clientAddress(addr), clientSocial(ssn),
clientEmployer(employer), clientIncome(income)
{
}
};
Then inside your main, you can do the following:
ClientInfo currentClient(clientName, clientAddress,
clientSocial, clientEmployer, clientIncome);
client.addClient(currentClient);
I think the only problem you're having is when you call getline, you're not passing in a parameter. In this case I think you need to use the newline delimeter.
while (infile2)
{
getline(infile2,clientName, '\n');
getline(infile2,clientAddress, '\n');
getline(infile2,clientSocial, '\n');
getline(infile2,clientEmployer, '\n');
getline(infile2,clientIncome, '\n');
client.addClient(clientName, clientAddress, clientSocial, clientEmployer, clientIncome);
}
I'm not sure on the '\n' syntax but this will read the file until it hits a newline and then move on to the next line.
I want to write a simple istream object, that would simply transform another istream.
I want to only implement readline (which would read a line from the original stream, would process it, and return the processed line), and have some generic code that upon read would use my read line, cache it, and give the required amount of bytes as output.
Is there any class that would allow me to do that?
For example
struct mystream : istreamByReadLine {
istream& s;
mystream(istream& _s):s(_s){}
virtual string getline() {
string line;
getline(s,line);
f(line);
return line;
}
}
class istreamByReadLine : istream {
... // implementing everything needed to be istream compatible, using my
... // getline() virtual method
}
Have you looked at boost.iostreams? It does most of the grunt work for you (possibly not for your exact use case, but for C++ standard library streams in general).
Are you sure this is the way to go? In similar cases, I've
either defined a class (e.g. Line), with a >> operator which
did what I wanted, and read that, e.g.:
Line line
while ( source >> line ) ...
The class itself can be very simple, with just a std::string
member, and an operator std::string() const function which
returns it. All of the filtering work would be done in the
std::istream& operator>>( std::istream&, Line& dest )
function. Or I've installed a filtering streambuf in front of the
normal streambuf ; Boost iostream has good support for
this.
I quite recently learned about the C++ classes friend keyword and the uses in serialization and now I need some help in getting it to work.
I have no problem serializing my class to a file, it's working great, however i'm having a hard time trying to read this file into a vector container. I'm sure I need a loop in my code that reads line by line, but since the class has different types I guess I can't use std::getline() and also maybe that approach wouldn't use the istream method i implemented?
A sample output file would be:
Person 1
2009
1
Person 2
2001
0
My code:
class SalesPeople {
friend ostream &operator<<(ostream &stream, SalesPeople salesppl);
friend istream &operator>>(istream &stream, SalesPeople &salesppl);
private:
string fullname;
int employeeID;
int startYear;
bool status;
};
ostream &operator<<(ostream &stream, SalesPeople salesppl)
{
stream << salesppl.fullname << endl;
stream << salesppl.startYear << endl;
stream << salesppl.status << endl;
stream << endl;
return stream;
}
istream &operator>>(istream &stream, SalesPeople &salesppl)
{
stream >> salesppl.fullname;
stream >> salesppl.startYear;
stream >> salesppl.status;
// not sure how to read that empty extra line here ?
return stream;
}
// need some help here trying to read the file into a vector<SalesPeople>
SalesPeople employee;
vector<SalesPeople> employees;
ifstream read("employees.dat", ios::in);
if (!read) {
cerr << "Unable to open input file.\n";
return 1;
}
// i am pretty sure i need a loop here and should go line by line
// to read all the records, however the class has different
// types and im not sure how to use the istream method here.
read >> employee;
employees.push_back(employee);
By the way, I know that the Boost library has a great serialization class, however I'm trying to learn how serialization would work using the STL library for now.
Thanks a lot in advance for any help that you can give me and for getting me in the right track!
It looks like you pretty much have all the code you need already! I copied your code and compiled it with some changes to read the SalesPeople in from a file in a loop. I will include the changes below, but since this is for your homework, you may just want to read and think about the following hints before looking at the code.
For reading the SalesPeople in a
loop, I would recommend that you take
a look at this FAQ. It has an
example of almost exactly what you
need. FAQ 15.4 will also help
you, I believe.
For your question on how to handle
the extra empty line when reading
from the file, check out this
link. You can very simply
extract whitespace this way.
As jfclavette suggested, I would
recommend looking into
std::getline for reading in the
SalesPerson's full name, since you
need everything on that line into one
string.
I have one question for you, though: what about the employeeID? I notice that it is being ignored in your sample code. Is that on purpose?
And now, if you still need help, you can check out the code I wrote to get this to work:
istream &operator>>(istream &stream, SalesPeople &salesppl)
{
//stream >> salesppl.fullname;
getline(stream, salesppl.fullname);
stream >> salesppl.startYear;
stream >> salesppl.status;
// not sure how to read that empty extra line here ?
stream >> ws;
return stream;
}
while(read >> employee)
{
// cout << employee; // to verify the input, uncomment this line
employees.push_back(employee);
}
Also, as jfclavette suggested, it may not be a bad idea to add some input validation (check the stream status after reading from it and verify that it is still good). Although I would recommend using the while() loop for the reasons stated in FAQ 15.5.
Not sure what your problem is. What exactly are you not understanding ? The fact that your names are composed of multiple tokens ? There's no magic way to do it, you might want to get the name trough getline(). Alternatively, you may want to specify the number of tokens when serializing and read the appropriate token count. ie, your file might look like.
2 Person 1
I assumed that Person was the first name and 1 the last name here. You might also enforce the notion that there's one first name, and one last name and just read each one separately.
You'll typically loop while (!ifstream.eof()) and read. Of course, you should always validate the inputs.
Also, why are you adding an extra endl between each record ? Serialized data need not be pretty. :)