I am making a statistics collector that reads the log of a music player and lets the user show top ten most played etc. As a noob project.
A line from the log looks like: "20:42:03 start E:\ROTATION\A\HÃ¥kan Lidbo - Dammlunga.mp3"
I have put this in a string using ifstream and getline.
Then making an array of chars of the string using
const char *charveqtur = newline.c_str();
Then I tried to sort i out with sscanf:
sscanf (charveqtur, "%d:%d:%d\tstart\t%s", &this->hour, &this->minute, &this->second, &this->filename);
The problem is that the filename is cut at the first space. I have also tried using istringstream instead but no breakthrough so far.
Which is the most convinient way of doing this? Thanks.
You can use some input stream to read the first integers and colons, and because the filename is the last entity, you can then use std::getline. However, even if your filename is not the last part, note that std::getline is quite a versatile function that accepts any delimiter.
A more advanced method would be to define your own type for filenames and overload operator>>(std::istream &, T const &) on it.
Here is a complete example using std::getline and stringstream with basic diagnostics and some reformatting:
#include <sstream> // for istringstream
#include <iostream> // for cout and cerr
#include <iomanip> // for setprecision
#include <cmath>
bool read (std::string const &line) {
char c = 0;
double length;
double rating;
std::string title;
std::istringstream ss;
ss.str (line);
ss >> length;
if (!ss.good()) { std::cerr << "invalid length\n"; return false; }
if (ss.get()!=':') { std::cerr << "expected colon\n"; return false; }
ss >> rating;
if (!ss.good()) { std::cerr << "invalid rating\n"; return false; }
if (ss.get()!=':') { std::cerr << "expected colon\n"; return false; }
std::getline (ss, title);
double sink;
std::cout << title << " ("
<< int(length) << ':' << 60*std::modf (length,&sink)
<< " min), your rating: " << rating << '\n';
return true;
}
int main () {
read ("30.25:5:Vivaldi - The four seasons.ogg");
read ("3.5:5:Cannibal Corpse - Evisceration Plague.ogg");
read ("meh");
return 0;
}
Output:
Vivaldi - The four seasons.ogg (30:15 min), your rating: 5
Cannibal Corpse - Evisceration Plague.ogg (3:30 min), your rating: 5
invalid length
Important: When parsing, you are sailing close to the security risks. Always be conscious and sensible and try to use tested and proven libraries where possible. This also implies that you do not use sscanf, which is not typesafe, error-prone and sometimes hard to get right.
Don't use C if you have C++, and used correctly, iostreams are even more convenient than printf/scanf+co.
You could perhaps do something like
int lastpos = 0;
if sscanf (charveqtur, "%d:%d:%d\tstart\t%n", &this->hour,
&this->minute, &this->second,
&lastpos) > 3 && lastpos >0) {
std::string filename = newline.substr(lastpos);
/* do something with filename */
}
Related
i am trying to get my file to remove the leading and trailing space but it does not work.
this is the txt file contents:
392402 wench
I have tried printing out my code, and this is what is displayed.
first: 392402 wench second:
I want it to display this instead
first: 392402 second: wench
this is my code
void readFile(const string &fileName) {
int limit;
ifstream ifs(fileName);
string::size_type position;
key_type item;
mapped_type count;
string line;
if (ifs.is_open()) {
ifs >> limit;
for (int i = 0; i < limit; i++) {
getline(ifs, line);
position = line.find(" ", 0);
auto c = line.substr(position + 1);
item = line.substr(0, position);
cout << "first: " << c << " second: " << item << endl;
value_type value(item, count);
values.push_back(value);
}
} else {
cout << "Can't open file.";
}
what am i doing wrong? Thank you
The two biggest mistakes you're making are (a) not checking your values for expected output as you go, and (b) not running your code in a debugger to see what is really happening. If you had, the values of position, c, and item would have been blatantly wrong, and you could then surmise where to go from there.
Belaying the highly-likely possibility that the loop iteration is broken from inception because you never consumed the remainder of the entry line containing input, let's look at the actual data and what you're asking of it with your code.
We read this entire line:
392402 wench
You then ask "find the first single-space string in this line" via this code:
position = line.find(" ", 0);
Well, that would be here:
392402 wench
^here
So position is zero (0). You then ask for the sub-string, starting a that position + 1, through the end of the string with this code:
auto c = line.substr(position + 1);
Therefore c now contains (leading space removed via the +1):
392402 wench
Now we build item, which is done with this line:
item = line.substr(0, position);
Remember, position is zero, so you're asking for the string, starting at location 0, length 0. As you can imagine, that isn't going to amount to anything. So now item is an empty string.
Finally, the output statement:
cout << "first: " << c << " second: " << item << endl;
will produce:
first: 392402 wench second:
I.e. exactly what you're seeing. And that's it. Clearly this is wrong.
Alternative
Use better error checking, value checking, and a string stream for per-line extraction. The following code doesn't give two cents about your type aliases (mainly because you didn't include them anyway and I'd rather not loft any guesses as to their origin).
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <limits>
// Expects a file with the following format:
// count
// name1 value1
// name2 value2
// ...
void readFile(const std::string &fileName)
{
std::ifstream ifs(fileName);
if (ifs.is_open())
{
int limit;
if (ifs >> limit && limit > 0)
{
// consume through end of line.
ifs.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// repeat until `limit` iterations or stream error/eof
std::string line;
for (int i = 0; i < limit && std::getline(ifs, line); i++)
{
std::istringstream iss(line);
// extract line values. Note these *can* be formatted
// extraction for things besides just strings
std::string first, second;
if (iss >> first >> second)
{
std::cout << "first: " << first << " second: " << second << '\n';
// TODO: whatever you want to do with first/second
}
}
}
ifs.close();
}
else
{
std::cerr << "Can't open file: " << fileName << '\n';
}
}
Note: The above code will NOT work for remaining-line-content as the expected second value. E.g. It will not process something like this as you may first expect:
10000 this is a multi-word description
will produce this:
first: 10000 second: this
which is considerably different than what you may be expecting:
first: 10000 second: this is a multi-word description
There was no suggestion in the original post such support was mandatory, though adding it wouldn't be terribly difficult to add. If it is a requirement, I leave that task to you.
I'm trying to read in a CSV file that contains rows of 3 people/patients, where col 1 is userid, col 2 is fname, col 3 is lname, col 4 is insurance, and col 5 is version that looks something like below.
Edit: Apologies, I simply copy/pasted my CSV spreadsheet in here, so it didn't show the commas before. Wouldn't it look something more like below? John below also pointed out that there are no commas after the version, and this seemed to fix the issue! Thanks so much John! ( trying to figure out how I can accept your answer :) )
nm92,Nate,Matthews,Aetna,1
sc91,Steve,Combs,Cigna,2
ml94,Morgan,Lands,BCBS,3
I'm trying to use getline() inside of a loop to read everything in, and it works fine for the first iteration, but getline() seems to be causing it to skip a value on the next iterations. Any idea how I can solve this?
I'm also not sure why the output looks like below, because I'm not seeing where the lines w/ "sc91" and "ml94" are being printed in the code. This is what the output of the current code looks like.
userid is: nm92
fname is: Nate
lname is: Matthews
insurance is: Aetna
version is: 1
sc91
userid is: Steve
fname is: Combs
lname is: Cigna
insurance is: 2
ml94
version is: Morgan
userid is: Lands
fname is: BCBS
lname is: 3
insurance is:
version is:
I've done a ton of research on differences between getline() and the >> stream operator, but most of the getline() materials seem to revolve around getting input from cin rather than reading from a file like here, so I'm thinking there's something going on w/ getline() and how it's reading the file that I'm not understanding. Unfortunately when I tried >> operator, that forces me to use the strtok() function, and I was struggling a lot with c strings and assigning them to an array of C++ strings.
#include <iostream>
#include <string> // for strings
#include <cstring> // for strtok()
#include <fstream> // for file streams
using namespace std;
struct enrollee
{
string userid = "";
string fname = "";
string lname = "";
string insurance = "";
string version = "";
};
int main()
{
const int ENROLL_SIZE = 1000; // used const instead of #define since the performance diff is negligible,
const int numCols = 5; // while const allows for greater utility/debugging bc it is known to the compiler ,
// while #define is a preprocessor directive
ifstream inputFile; // create input file stream for reading only
struct enrollee enrollArray[ENROLL_SIZE]; // array of structs to store each enrollee and their respective data
int arrayPos = 0;
// open the input file to read
inputFile.open("input.csv");
// read the file until we reach the end
while(!inputFile.eof())
{
//string inputBuffer; // buffer to store input, which will hold an entire excel row w/ cells delimited by commas
// must be a c string since strtok() only takes c string as input
string tokensArray[numCols];
string userid = "";
string fname = "";
string lname = "";
string insurance = "";
string sversion = "";
//int version = -1;
//getline(inputFile,inputBuffer,',');
//cout << inputBuffer << endl;
getline(inputFile,userid,',');
getline(inputFile,fname,',');
getline(inputFile,lname,',');
getline(inputFile,insurance,',');
getline(inputFile,sversion,',');
enrollArray[0].userid = userid;
enrollArray[0].fname = fname;
enrollArray[0].lname = lname;
enrollArray[0].insurance = insurance;
enrollArray[0].version = sversion;
cout << "userid is: " << enrollArray[0].userid << endl;
cout << "fname is: " << enrollArray[0].fname << endl;
cout << "lname is: " << enrollArray[0].lname << endl;
cout << "insurance is: " << enrollArray[0].insurance << endl;
cout << "version is: " << enrollArray[0].version << endl;
}
}
Your problem is that there is no comma after the final data item in each line, so
getline(inputFile,sversion,',');
is incorrect because it reads to the next comma, which is actually on the next line after the user id of the next patient. This explains the output you see where the user id of the next patent gets output with the version.
To fix this simply replace the code above with
getline(inputFile,sversion);
which will read to the end of line as required.
Regarding your function. If you look at the structure of the source file, then you will see that it contains 5 strings, separated by ",". So a typical CSV file.
A call to std::getline will read a complete line with the 5 strings. In your code you are trying to call std::getline for each single string, followed by a comma. Commaa is not present after the last string. That will not work. You should also use getline to get a complete line.
You need to read the whole line and then tokenize it.
I will show you an example on how to do that with the std::sregex_token_iterator. That is very simple. Additionally, we will overwrite the inserter and extracot operator. With that, you can easiyl read and write "enrollee" data like Enrollee e{}; std::cout << e;
Additionally I use C++ algorithms. That makes life very easy. Input and Output are a one-liner in main.
Please see:
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <regex>
struct Enrollee
{
// Data
std::string userid{};
std::string fname{};
std::string lname{};
std::string insurance{};
std::string version{};
// Overload Extractor Operator to read data from somewhere
friend std::istream& operator >> (std::istream &is, Enrollee& e) {
std::vector<std::string> wordsInLine{}; // Here we will store all words that we read in onle line;
std::string wholeLine; // Temporary storage for the complete line that we will get by getline
std::regex separator("[ \\;\\,]"); ; // Separator for a CSV file
std::getline(is, wholeLine); // Read one complete line and split it into parts
std::copy(std::sregex_token_iterator(wholeLine.begin(), wholeLine.end(), separator, -1), std::sregex_token_iterator(), std::back_inserter(wordsInLine));
// If we have read all expted strings, then store them in our struct
if (wordsInLine.size() == 5) {
e.userid = wordsInLine[0];
e.fname = wordsInLine[1];
e.lname = wordsInLine[2];
e.insurance = wordsInLine[3];
e.version = wordsInLine[4];
}
return is;
}
// Overload Inserter operator. Insert data into output stream
friend std::ostream& operator << (std::ostream& os, const Enrollee& e) {
return os << "userid is: " << e.userid << "\nfname is: " << e.fname << "\nlname is: " << e.lname << "\ninsurance is: " << e.insurance << "\nversion is: " << e.version << '\n';
}
};
int main()
{
// Her we will store all Enrollee data in a dynamic growing vector
std::vector<Enrollee> enrollmentData{};
// Define inputFileStream and open the csv
std::ifstream inputFileStream("r:\\input.csv");
// If we could open the file
if (inputFileStream) {
// Then read all csv data
std::copy(std::istream_iterator<Enrollee>(inputFileStream), std::istream_iterator<Enrollee>(), std::back_inserter(enrollmentData));
// For Debug Purposes: Print all data to cout
std::copy(enrollmentData.begin(), enrollmentData.end(), std::ostream_iterator<Enrollee>(std::cout, "\n"));
}
else {
std::cerr << "Could not open file 'input.csv'\n";
}
}
This will read the input file "input.csv" containing
nm92,Nate,Matthews,Aetna,1
sc91,Steve,Combs,Cigna,2
ml94,Morgan,Lands,BCBS,3
And show as output:
userid is: nm92
fname is: Nate
lname is: Matthews
insurance is: Aetna
version is: 1
userid is: sc91
fname is: Steve
lname is: Combs
insurance is: Cigna
version is: 2
userid is: ml94
fname is: Morgan
lname is: Lands
insurance is: BCBS
version is: 3
That is only an idea, but it could help you. It's a piece of code of one project I am working on:
std::vector<std::string> ARDatabase::split(const std::string& line, char delimiter)
{
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(line);
while (std::getline(tokenStream, token, delimiter))
{
tokens.push_back(token);
}
return tokens;
}
void ARDatabase::read_csv_map(std::string root_csv_map)
{
qDebug() << "Starting to read the people database...";
std::ifstream file(root_csv_map);
std::string str;
while (std::getline(file, str))
{
std::vector<std::string> tokens = split(str, ' ');
std::vector<std::string> splitnames = split(tokens.at(1), '_');
std::string name_w_spaces;
for(auto i: splitnames) name_w_spaces = name_w_spaces + i + " ";
people_names.insert(std::make_pair(stoi(tokens.at(0)), name_w_spaces));
people_images.insert(std::make_pair(stoi(tokens.at(0)), std::string("database/images/" + tokens.at(2))));
}
}
Instead of std::vector, you might want to use other container more suitable for your case. And the last example is made for the input format of my case. You can modify it easily for adapting it to your code.
Lets say I want to input the hours, minutes and seconds from the first line of a file and store them to 3 different variables, hrs, mins and sec respectively.
I cant figure out an easy way to skip reading the colon character (":").
Input file example:
12:49:00
Store:
hrs = 12
mins = 59
sec = 00
You can use std::regex to match, range-check and validate your input all at once.
#include <iostream>
#include <regex>
#include <string>
int main()
{
const std::regex time_regex("(\\d|[0,1]\\d|2[0-3]):([0-5]\\d):([0-5]\\d)");
std::smatch time_match;
std::string line;
while (std::getline(std::cin, line))
{
if (std::regex_match(line, time_match, time_regex))
{
int hours = std::stoi(time_match[1]);
int minutes = std::stoi(time_match[2]);
int seconds = std::stoi(time_match[3]);
std::cout << "h=" << hours << " m=" << minutes << " s=" << seconds << std::endl;
}
else
{
std::cout << "Invalid time: " << line << std::endl;
}
}
return 0;
}
See this example live here.
Breaking down the regular expression (\\d|[0,1]\\d|2[0-3]):([0-5]\\d):([0-5]\\d):
\d|[0,1]\d|2[0-3] matches the hour (24-hour time) which is one of:
\d : 0-9
[0,1]\d : 01-19
2[0-3] : 20-23
[0-5]\d matches the minutes: two digits 00-59
[0-5]\d matches the seconds: two digits 00-59, as above.
An alternative not using a temporary character for skipping the colon:
#include <iostream>
int main()
{
int h,m,s;
std::cin >> h;
std::cin.ignore(1) >> m;
std::cin.ignore(1) >> s;
std::cout << h << ':' << m << ':' << s << std::endl;
return 0;
}
This seems to work:
int h, m, s;
char c;
cin >> h >> c >> m >> c >> s;
You just skip : symbol this way. I don't know whether it's a good solution.
With cin.ignore:
cin >> h;
cin.ignore(1);
cin >> m;
cin.ignore(1);
cin >> s;
There are already several good answers and one that has already been accepted; however I like to propose my solution not only as a valid answer to your problem but also in regards to a good design practice. IMHO when it involves reading information from a file and storing it's contents to variables or data structures I prefer to do it in a specific way. I like to separate the functionality and responsibility of specific operations into their own functions:
1: I first like to have a function to open a file, read the contents and to store the information into either a string, a stream or some large buffer. Once the appropriate amount of information is read from the file, then the function will close the file handle as we are done with it and then return back the results. There are several ways to do this yet they are all similar.
a: Read a single line from the file and return back a string or a stream.
b: Read in all information form the file line by line and store each line into its own string or stream and return back a vector of those strings or streams.
c: Read in all of the contents of the file into a single string, stream or large buffer and return that back.
2: After I have the contents of that file then I will typically call a function that will parse that data and these functions will vary depending on the type of content that needs to be parsed based on the data structures that will be used. Also, these parsing functions will call a function that will split the string into a vector of strings called tokens. After the split string function is called then the parsing of data will use the string manipulators-converters to convert a string to the required built in types that are needed for the current data structure that is in use and store them into the data structure that is passed in by reference.
3: There are two variations of my splitString function.
a: One takes a single character as a delimiter.
b: The other will take a string as its delimiter.
c: Both functions will return a vector of strings, based on the delimiter used.
Here is an example of my code using this text file for input.
time.txt
4:32:52
main.cpp
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
#include <iostream>
#include <exception>
struct Time {
int hours;
int minutes;
int seconds;
};
std::vector<std::string> splitString( const std::string& s, char delimiter ) {
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream( s );
while( std::getline( tokenStream, token, delimiter ) ) {
tokens.push_back( token );
}
return tokens;
}
std::string getLineFromFile( const char* filename ) {
std::ifstream file( filename );
if( !file ) {
std::stringstream stream;
stream << "failed to open file " << filename << '\n';
throw std::runtime_error( stream.str() );
}
std::string line;
std::getline( file, line );
file.close();
return line;
}
void parseLine( const std::string& fileContents, Time& time ) {
std::vector<std::string> output = splitString( fileContents, ':' );
// This is where you would want to do your sanity check to make sure
// that the contents from the file are valid inputs before converting
// them to the appropriate types and storing them into your data structure.
time.hours = std::stoi( output.at( 0 ) );
time.minutes = std::stoi( output.at( 1 ) );
time.seconds = std::stoi( output.at( 2 ) );
}
int main() {
try {
Time t;
std::string line = getLineFromFile( "time.txt" );
parseLine( line, t );
std::cout << "Hours: " << t.hours << '\n'
<< "Minutes: " << t.minutes << '\n'
<< "Seconds: " << t.seconds << "\n\n";
} catch( std::runtime_error& e ) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Output:
Hours: 4
Minutes: 32
Seconds: 52
Now as you can see in this particular situation the functions that are being used here is designed only to read a single line from the file and of course the very first line from the file. I have other functions in my library not shown here that will read each line of a file until there are no more lines to read, or read all of the file into a single buffer. I have another version of split string that will take a string as its delimiter instead of a single character. Finally for the parsing function, each parsing function will end up being unique due to the fact that it will rely on the data structure that you are trying to use.
This allows the code to be readable as each function does what it is supposed to do and nothing more. I prefer this design over the fact of trying to get information from a file and trying to parse it while the file is open. Too many things can go wrong while the file is open and if the data is read wrong or corrupted but to the point where the compiler doesn't complain about it, then your variables or data structures may contain invalid information without you being aware of it. At least in this way you can open the file, get what you need from the file and store it into a string or a vector of strings, close the file when done reading and return back the contents. Then it becomes the parsing function's responsibility to test the data after it has been tokenized. Now, in the current parsing function that I shown above I did not do any sanity check to keep things simple, but that is where you would test your data to see if the information is valid before returning back your populated data structure.
If you are interested in another version of this where there are multiple lines being read in from the file, just comment a request and I will append it to this answer.
So, I have a file that contains a pattern of a string then an int alternating line by line.
Something like this:
John McClane
30
James Bond
150
Indiana Jones
50
In this example, I would set John McClane to a string variable and then 30 to an integer variable. My issue is dealing with two types. I want to use getline(), but that only works with strings.
Is there an efficient or "right" way of doing this?
There are a number of approaches you could try.
Get string input, and convert to an integer if valid
Convert every second string to an integer
Try to read an integer when you expect one (just using cin >> in;). If you want a robust program, you can check validity with cin.good()
I don't know if there is a "right" way of doing this per say, but it's not a very taxing operation, so whatever you choose should be fine.
You could make a variable like this
string ibuf;
Then convert it to an integer doing this
getline(cin, ibuf);
(Whatever your int variable is) = strtol(ibuf.c_str(), NULL, 10);
One thing about C++ is that there are a large number of ways to accomplish any one task. One way to get integers from strings is to use a stringstream. There is a tutorial on stringstreams here
As for your problem with reading the alternating file, consider the following pseudocode:
boolean isInt = false;
while(fileIsNotOver) {
//getline
if(isInt) {
//use stringstream to get int here
} else {
//do whatever with the name here
}
isInt = !isInt;
}
I don't know if this fully works as i didn't tested it however it just compiles fine and answer should be something like this i think.
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
using namespace std;
int main()
{
int counter = 0;
int number;
string test_string;
ifstream myfile ("example.txt");
if (myfile.is_open())
{
while ( getline (myfile,test_string) )
{
cout << test_string << '\n';
++counter;
if(counter % 2 == 0 ){
number = atoi(test_string.c_str());
cout << number << '\n';
}else{
cout << test_string << '\n';
}
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
You can try like this to read a string then an int alternating line by line.
#include<iostream>
#include<string>
#include<cstdio>
using namespace std;
int main()
{
string name;
int number;
freopen("input.txt", "r", stdin);
while (getline(cin, name))
{
cin >> number;
/*
process the input here
...
...
*/
getline(cin, name); // just to read the new line and/or spaces after the integer
//getchar(); //you can use getchar() instead of getline(cin, name) if there is no spaces after the integer
}
return 0;
}
Thanks !!!
I wrote the code below that successfully gets a random line from a file; however, I need to be able to modify one of the lines, so I need to be able to get the line character by character.
How can I change my code to do this?
Use std::istream::get instead of std::getline. Just read your string character by character until you reach \n, EOF or other errors. I also recommend you read the full std::istream reference.
Good luck with your homework!
UPDATE:
OK, I don't think an example will hurt. Here is how I'd do it if I were you:
#include <string>
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
static std::string
answer (const string & question)
{
std::string answer;
const string filename = "answerfile.txt";
ifstream file (filename.c_str ());
if (!file)
{
cerr << "Can't open '" << filename << "' file.\n";
exit (1);
}
for (int i = 0, r = rand () % 5; i <= r; ++i)
{
answer.clear ();
char c;
while (file.get (c).good () && c != '\n')
{
if (c == 'i') c = 'I'; // Replace character? :)
answer.append (1, c);
}
}
return answer;
}
int
main ()
{
srand (time (NULL));
string question;
cout << "Please enter a question: " << flush;
cin >> question;
cout << answer (question) << endl;
}
... the only thing is that I have no idea why do you need to read string char by char in order to modify it. You can modify std::string object, which is even easier. Let's say you want to replace "I think" with "what if"? You might be better off reading more about
std::string and using find, erase, replace etc.
UPDATE 2:
What happens with your latest code is simply this - you open a file, then you get its content character by character until you reach newline (\n). So in either case you will end up reading the first line and then your do-while loop will terminate. If you look into my example, I did while loop that reads line until \n inside a for loop. So that is basically what you should do - repeat your do-while loop for as many times as many lines you want/can get from that file. For example, something like this will read you two lines:
for (int i = 1; i <= 2; ++i)
{
do
{
answerfile.get (answer);
cout << answer << " (from line " << i << ")\n";
}
while (answer != '\n');
}