seekg doesn't go to beginning of file - c++

I'm trying to make a random name generator. The problem is that in order to get the count of lines in the file, I have to loop through it.
So when I need to loop through it again in getRandomName() to get a name, it has already reached the end of the file
I tried solving the issue with seekg(0, std::ios::beg) but it doesn't work for some reason.
int getLineCount(std::fstream &names) {
int count{};
while (names) {
std::string name;
getline(names, name);
++count;
};
// last line is empty
return count - 1;
}
std::string getRandomName(std::fstream &names, int lineCount) {
int randomNum{getRandomNumber(1, lineCount)};
std::string name;
names.seekg(1, std::ios::beg); // here i try to go to the beginning but it doesnt work
for (int i{0}; i < randomNum; ++i) {
names >> name;
};
return name;
};
int main() {
std::srand(static_cast<unsigned int>(std::time(nullptr)));
std::rand();
std::fstream names{"names.txt"};
int lineCount{getLineCount(names)};
std::cout << getRandomName(names, lineCount);
}

The problem is that a file stream considers reaching the end of the file as an error condition and sets the according bits, both the fail-bit and the EOF-bit. As long as this state persists, any further file operations fail. You can set the stream back to the normal operating state by clearing the error state, though – if you do so, then you'll be able to proceed as intended.
If need those lookups frequently then it might be worth to consider buffering the data lines within a std::vector<std::string> – unless if you have to handle extremely large data (thus provoking paging effects) this would be far more efficient. Even with paging effects, but with large enough disk space available you still get better for every lookup as you'd have to load at most one memory page back from disk.
If you need the lookup just once then you might get along without the getLineCount function entirely – select a random value from entire maximum range and just count the number of lines until you found the desired line – or the end of file got reached. If the latter happens, then recalculate the random index based upon the number of lines found and iterate of over the file again. The larger your file is, the greater is the chance that you only need to iterate once, and if you still need to do twice, nothing is lost anyway... Note, though, that this approach requires your random number generator generating equally distributed random numbers!
This would work for multiple calls as well, though the chance of a benefit get's smaller as with every further call the chance of reading beyond file size at least once increases.

Your getLineCount() functions reads with getline() through the file until nothing can be read anymore. When it arrives at the end, an error state is set, with eofbit.
All subsequent actions on the stream will fail, including seekg(0, std::ios::beg);, until you names.clear(); the error state.
By the way, looping on getline() avoids getline() to fail in the loop body, and makes the -1 unnecessary. Another thing you could do is to make your function neutral for the read position of the file. It's optional bu would me more consistent with the name of your function which suggests that it just gets something, not that it consumes the stream to the end.
int getLineCount(std::fstream &names) {
int count{};
std::string name;
auto old_pos = names.tellg(); // backup current position
while (getline(names, name))
++count;
names.clear(); // reset eof error caused by loop
names.seekg (old_pos, std::ios::beg); // restore position
return count;
}
Not related
Your random position might lead to inconsistencies, if names on a line can include whitespaces, because >> reads space separated strings and not full lines. E.g. if your file has two lines:
Bjarne Stroustrup
B.W.Kernighan
Your random read could return Bjarne or Stroustrup but never B.W.Kernighan because there are 2 lines but 3 space separated strings. So better read the random line as you count them, using getline() again.

Your first step should start from adding logs (I've also fixed minor issues, like inconsistent reading of data, 1 instead 0 and so on).
#define LOG(x) std::cerr << __LINE__ << " " #x " = "<< x << '\n'
int getRandomNumber(int a, int b)
{
static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_int_distribution<int> distrib(a, b);
return distrib(gen);
}
int getLineCount(std::istream &names) {
int count{};
std::string name;
while (getline(names, name)) {
++count;
LOG(count);
LOG(names.tellg());
};
return count - name.empty();
}
std::string getRandomName(std::istream &names, int lineCount) {
int randomNum{getRandomNumber(1, lineCount)};
LOG(randomNum);
std::string name;
LOG(names.tellg());
names.seekg(0, std::ios::beg);
LOG(names.tellg());
for (int i{0}; i < randomNum; ++i) {
getline(names, name);
};
return name;
};
int main() {
std::ifstream names{"names.txt"};
LOG(names.tellg());
int lineCount{getLineCount(names)};
LOG(lineCount);
std::cout << getRandomName(names, lineCount);
}
This produces this output https://wandbox.org/permlink/EPLHqMBg1s8Awe9F :
44 names.tellg() = 0
23 count = 1
24 names.tellg() = 13
23 count = 2
24 names.tellg() = 27
23 count = 3
24 names.tellg() = 40
23 count = 4
24 names.tellg() = 53
23 count = 5
24 names.tellg() = -1
47 lineCount = 5
31 randomNum = 4
33 names.tellg() = -1
35 names.tellg() = -1
-1 indicates that stream is in error state.
And this is obvious you have read file to the end, so there was attempt to read beound file and error flag is set.
When error flag is set, stream is unusable until flag is cleared. So just adding names.clear(); in proper place fixes issue: https://wandbox.org/permlink/Us6b3Jw3v6JFwlpX

In your getLineCount() function use while (names.peek() != EOF) instead.

Related

Is there a data structure for implementing a function equivalent to 'tail -n' command in C++?

I want to write a function equivalent to the Linux tail -n command in C++. While, I parsed over the data of that file line-by-line thereby incrementing the line count, if the file size gets really big(~gigabytes), this method will take a lot of time! Is there a better approach or a data structure to implement this function?
Here are my 2 methods:
int File::countlines()
{
int lineCount = 0;
string str;
if (file)
{
while (getline(file, str))
{
lineCount += 1;
}
}
return lineCount;
}
void File::printlines()
{
int lineCount = 0;
string line;
if (file)
{
lineCount = countlines();
file.clear();
file.seekg(ios::beg);
if (lineCount <= 10)
{
while (getline(file, line))
{
cout << line << endl;
}
}
else
{
int position = lineCount - 10;
while (position--)
{
getline(file, line);
}
while (getline(file, line))
{
cout << line << endl;
}
}
}
}
This method is time consuming if the file size increases, so I want to either replace it with another data structure, or write a more efficient code.
One of the things that is slowing down your program is reading the file twice, so you could keep the last n EOL positions (n=10 in your program) and the most convenient data structure is a circular buffer but this isn't provided by the standard library as far as I know (boost has one). It can be implemented by an std::vector with size n, with an index where a modulo of n is done after incrementing.
With that circular buffer, you can jump immediately to the lowest offset (next one if buffer is full) in the file and print the needed lines.
When I've done this, I've done a generous estimate of the maximum length of a line (e.g., one kilobyte), seeked to that distance from the end, and started reading lines into a circular buffer until the end of the file.
In nearly every case, you get more than n lines, so you just print out the contents of the circular buffer, and you're done. Note, however, that you do need to assure that you read more than n lines, not just n lines. The first line you read will usually only be a partial line, so if you read exactly n lines, the first would probably be only a partial line.
On rare occasion, you haven't gotten the required number of lines, so you seek back twice as far (or other factor of your choice), and restart. If you want to get really fancy, you can extrapolate the number of lines you'll need based on the average length of the lines you did read (but honestly, this is such a rare situation it's not worth a lot of work to optimize it).
This normally works essentially instantly, regardless of file size. I suppose (in theory) for a file with incredibly long lines, it would get slower, but if that's the case, the user has probably made a mistake, and tried to tail something that isn't a text file (which is generally useless anyway).

Summing comma separated ints from a text file and then storing to an array in C++

I was tasked to read 3 rows of 5 comma separated values from a text file, sum up each column, and store the result in an array called bins. I am struggling to read the ints from the text file as they are comma separated. I first need to clarify how to read just the ints.
My next thought was to store the ints from the file into an array called "calc", and use the index of each element to sum up the values. I would then store these results into the "bins" array.
Here is some code I have tried to read the comma separated ints yet I cannot seem to get it to work.
int a,b,c,d,e,f,g,h,i,j,k,l,m,n,o;
int calc[15] = {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o};
ifstream myfile;
myfile.open("values.txt");
for(int i = 0; i <= 15; i++)
{
myfile >> calc[i];
myfile.close();
a = calc[0];
b = calc[1];
c = calc[2];
d = calc[3];
e = calc[4];
f = calc[5];
g = calc[6];
h = calc[7];
i = calc[8];
j = calc[9];
k = calc[10];
l = calc[11];
m = calc[12];
n = calc[13];
o = calc[14];
cout << calc[i] << endl;
}
I am really new to working with code and I dont quite understand how to work with values in this manner. It is a simple task yet I cannot seem how to implement it with code.
I am really new to working with code and I dont[sic] quite understand how to work with values in this manner.
OK, I have several tips for you:
① separate your tasks
You ran into a hitch parsing the input in the supplied format, dealing with the comma. Parsing the supplied input files is a totally different problem from the real work, which is summing the columns. Write them separately in the code.
In general you should isolate the "real work" in its own function and have it take parameters as input and returns results as a function return value. The input and output are written separately.
That gives you the added bonus of automating the testing by calling the "work" function with built-in test cases. In this case, it allows you to defer figuring out the parsing. You just pass in test data for now, to get the "work" part working, and then you can come back to parsing the input. Then, when you do need help, it will be specific to "parsing comma separated values" and have nothing to do with why you want them.
② To handle groups of values, you use the array.
This means subscripting or iterating, using loops (or library algorithms) to take what you want to do, written once, and apply it to each value in the array.
Given arrays input and sum, you can accumulate the current row (input) into the running sum with code like this:
for (size_t i = 0; i < COLS; ++i) {
sum[i] += input[i];
}
overall program sketch
open the file
repeat three times:
read a row of input
accumulate the sum with the new input
print the results
Note, as explained in the first topic, that read a row and accumulate the sum are separate functions and separate sub-tasks to figure out. This is called top-down decomposition of a problem.
It's best to use parameters for input and return for output of the function, but for this simple task I'll just use a global variable. Passing/returning is probably harder than the task you are learning! Note though that this is unrealistic in that in real code you would not want to use global variables like this. However, you might turn this into an "object", which you'll learn later.
#include <fstream>
constexpr size_t ROWS = 3;
constexpr size_t COLS = 5;
int input[COLS];
int sum[COLS];
std::ifstream infile;
int main()
{
infile.open("values.txt");
// todo: check for success and feedback to the user if failed
// skipped: zero out the sum array. Global variable start at 0,
// but more generally, you would need to initialize this.
for (size_t row= 0; row < ROWS; ++row) {
read_row();
sum_row();
}
print_results();
}
The sum_row function is what you saw earlier.
Note that with top-down decomposition, you can stub out parts that you will work on later. In particular, you can have read_row return hard-coded result at first, or read from a different format, so you can test the overall program. Then, go back and get that part working for real.
Top-Down Decomposition is critical for any kind of programming project.
Oops... most of your code is useless, and what remains is not really good.
Writing good programs is not a matter of adding C++ instructions one after the other. You must first design the overall structure of your program.
Here you have an input file containing lines of 5 comma separated values and want to compute an array (of size 5) containing the sum of the columns.
Let go from a high level
open the file
loop over the lines
loop 5 times:
read a field up to a comma (or end of the line)
decode that field into an int value
sum that value into an array
close the file
Ok, to be able to sum the values into an array, we will have to define the array before the loop and initialize its elements to 0.
Ok, C++ provide std::ifstream to read a file, std::getline to read a stream up to a delimiter (default being newline), std::istringstream to read the content of a string as an input stream and std::stoi to decode a string representing an int value.
Once this is done, but only after:
the program structure is clearly designed
the required tools from the standard library have been identified
it is possible to sit down in front of your keyboard and start coding.
BTW, this program will never require the declaration of 15 variables a to o nor an array of 15 integers: only int bins[5]...
It could be (tests omitted for brievety):
int bins[5] = {0}; // initializing the first value is enough, others will be 0
std::ifstream in("values.txt");
std::string line;
while (std::getline(in, line)) {
// std::cout << line << '\n'; // uncomment for debug
std::stringstream ss(line);
for(int& val: bins) { // directly loop over the bins array
std::string field;
std::getline(ss, field, ',');
val += std::atoi(field.c_str());
}
}
Of course, for a professional grade (or simply robust) program, every input operation should be followed by a test on the stream...
You can use the std::getline function within the string library to get each comma separated integer.
std::ifstream myfile("values.txt");
for(int i = 0; i < 15; i++)
{
std::string integer_as_string;
std::getline(myfile, integer_as_string, ',');
calc[i] = std::stoi(integer_as_string);
}
myfile.close();
Here we specify that the getline function will read a line of characters in the input until a , character is found. This string is assigned to the integer_as_string variable which will then be converted to an integer and gets assigned to the array.
Also note that i <= 15 will result in undefined behavior. You can further read it here: Wikipedia. And the myfile.close() function was set inside the for loop. This means that in every iteration, you will be closing the file. This is not needed. I think what your looking for is something like this.
std::ifstream myfile("values.txt");
for(int i = 0; i < 15; i++)
{
std::string integer_as_string;
std::getline(myfile, integer_as_string, ',');
calc[i] = std::stoi(integer_as_string);
std::cout << calc[i] << std::endl;
}
myfile.close();
a = calc[0];
b = calc[1];
c = calc[2];
d = calc[3];
e = calc[4];
f = calc[5];
g = calc[6];
h = calc[7];
i = calc[8];
j = calc[9];
k = calc[10];
l = calc[11];
m = calc[12];
n = calc[13];
o = calc[14];
References:
std::stoi
Why is "using namespace std;" considered bad practice?
First, your array have element with indices from 0 to 14, thus for(int i = 0; i <= 15; i++) should be for(int i = 0; i < 15; i++)
The loop itself might benefit from error-checking. What if file contains less than 15 values?
for(int i = 0; i <= 15; i++)
{
// you want check status of myfile here.
}
myfile >> calc[i] wouldn't work well with commas unless you add comma to a separator class for that stream. Albeit that can be done that's a little large change and one can use getline (see answers here for examples) instead to specify separator.
If you want named variables to refer to element of array, you can make them references and structurally bind them to array (or other tuple-like data structure, e.g. struct, etc.) provided you have access to C++17
int calc[15] = {};
auto& [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o] = calc;
a would become a reference to calc[0], b to calc[1] and so on.

Logic for reading rows and columns from a text file (textparser) C++

I'm really stuck with this problem I'm having for reading rows and columns from a text file. We're using text files that our prof gave us. I have the functionality running so when the user in puts "numrows (file)" the number of rows in that file prints out.
However, every time I enter the text files, it's giving me 19 for both. The first text file only has 4 rows and the other one has 7. I know my logic is wrong, but I have no idea how to fix it.
Here's what I have for the numrows function:
int numrows(string line) {
ifstream ifs;
int i;
int row = 0;
int array [10] = {0};
while (ifs.good()) {
while (getline(ifs, line)) {
istringstream stream(line);
row = 0;
while(stream >>i) {
array[row] = i;
row++;
}
}
}
}
and here's the numcols:
int numcols(string line) {
int col = 0;
int i;
int arrayA[10] = {0};
ifstream ifs;
while (ifs.good()) {
istringstream streamA(line);
col = 0;
while (streamA >>i){
arrayA[col] = i;
col++;
}
}
}
edit: #chris yes, I wasn't sure what value to return as well. Here's my main:
int main() {
string fname, line;
ifstream ifs;
cout << "---- Enter a file name : ";
while (getline(cin, fname)) { // Ctrl-Z/D to quit!
// tries to open the file whose name is in string fname
ifs.open(fname.c_str());
if(fname.substr(0,8)=="numrows ") {
line.clear();
for (int i = 8; i<fname.length(); i++) {
line = line+fname[i];
}
cout << numrows (line) << endl;
ifs.close();
}
}
return 0;
}
This problem can be more easily solved by opening the text file as an ifstream, and then using std::get to process your input.
You can try for comparison against '\n' as the end of line character, and implement a pair of counters, one for columns on a line, the other for lines.
If you have variable length columns, you might want to store the values of (numColumns in a line) in a std::vector<int>, using myVector.push_back(numColumns) or similar.
Both links are to the cplusplus.com/reference section, which can provide a large amount of information about C++ and the STL.
Edited-in overview of possible workflow
You want one program, which will take a filename, and an 'operation', in this case "numrows" or "numcols". As such, your first steps are to find out the filename, and operation.
Your current implementation of this (in your question, after editing) won't work. Using cin should however be fine. Place this earlier in your main(), before opening a file.
Use substr like you have, or alternatively, search for a space character. Assume that the input after this is your filename, and the input in the first section is your operation. Store these values.
After this, try to open your file. If the file opens successfully, continue. If it won't open, then complain to the user for a bad input, and go back to the beginning, and ask again.
Once you have your file successfully open, check which type of calculation you want to run. Counting a number of rows is fairly easy - you can go through the file one character at a time, and count the number that are equal to '\n', the line-end character. Some files might use carriage-returns, line-feeds, etc - these have different characters, but are both a) unlikely to be what you have and b) easily looked up!
A number of columns is more complicated, because your rows might not all have the same number of columns. If your input is 1 25 21 abs 3k, do you want the value to be 5? If so, you can count the number of space characters on the line and add one. If instead, you want a value of 14 (each character and each space), then just count the characters based on the number of times you call get() before reaching a '\n' character. The use of a vector as explained below to store these values might be of interest.
Having calculated these two values (or value and set of values), you can output based on the value of your 'operation' variable. For example,
if (storedOperationName == "numcols") {
cout<< "The number of values in each column is " << numColsVal << endl;
}
If you have a vector of column values, you could output all of them, using
for (int pos = 0; pos < numColsVal.size(); pos++) {
cout<< numColsVal[pos] << " ";
}
Following all of this, you can return a value from your main() of 0, or you can just end the program (C++ now considers no return value from main to a be a return of 0), or you can ask for another filename, and repeat until some other method is used to end the program.
Further details
std::get() with no arguments will return the next character of an ifstream, using the example code format
std::ifstream myFileStream;
myFileStream.open("myFileName.txt");
nextCharacter = myFileStream.get(); // You should, before this, implement a loop.
// A possible loop condition might include something like `while myFileStream.good()`
// See the linked page on std::get()
if (nextCharacter == '\n')
{ // You have a line break here }
You could use this type of structure, along with a pair of counters as described earlier, to count the number of characters on a line, and the number of lines before the EOF (end of file).
If you want to store the number of characters on a line, for each line, you could use
std::vector<int> charPerLine;
int numberOfCharactersOnThisLine = 0;
while (...)
{
numberOfCharactersOnThisLine = 0
// Other parts of the loop here, including a numberOfCharactersOnThisLine++; statement
if (endOfLineCondition)
{
charPerLine.push_back(numberOfCharactersOnThisLine); // This stores the value in the vector
}
}
You should #include <vector> and either specific std:: before, or use a using namespace std; statement near the top. People will advise against using namespaces like this, but it can be convenient (which is also a good reason to avoid it, sort of!)

Reading a truth table in from plain text, translating it to a map<int,list<int>> in C++

I'm writing a file parser for standard C++ (no third-parties like Boost, unfortunately)...
I'm dealing with a situation where I have a plain-text file formatted like this:
1 ..header line 1, unimportant
2 ..header line 2, unimportant
3 ..header line 3, unimportant
4 1 0 0 0 0 0 0 1
5 2 0 1 0 2 1 0 0
...skipping ahead
14 11 1 0 0 0 0 1 1
15 12 0 0 1 0 0 1 2
16 13 2 0 0 0 1 0 0
...etc
(Note: The first column, 1 - 16, are line numbers. The skip ahead is meant to represent the gap of 8 spaces from the start of each line gets shorter as the second column, 1- 13, gets longer and longer numbers.
This text file denotes a truth table whereby items must be grouped by the columns, and each group will be composed of corresponding numbers from the first column. For instance, by the end of parsing this example, a map of type <int, list<int>> should look like (assuming there are no truths between lines 6 and 13):
[1: {11, 13}]
[2: {5, 15}]
[3: {12}]
[4: {5}]
[5: {5,16}]
[6: {14,15}]
[7: {4,14,15}]
In general, the number of columns in the text file can change, meaning the number of groups will change, so this must be accounted for. The number of rows is also variable, but will both will always start at 1 and the columns will not be numbered (but we can do that ourselves).
Now, were I to do this in Java I'd have a working solution rather quickly. However, I've never done work in C++ and am having trouble figuring out how to perform the operations properly, between its different structures and syntax. Despite scouring and finding lots of good guides, my lack of C++ foundation makes it hard to understand even the syntax differences that, I speculate, must be very basic.
Still, I've designed procedure, and it should work according to the following pseudocode:
//Begin Parse
//Create filereader "strmFileIn"
//To get past the first three lines, which will always be needless header info
string dummyLine;
for (i = 1; i <= 3; i++)
getline(strmFileIn, strDummyLine);
//Read first line to get count of how many groups are present
//(Copied from internet: gets the first line and puts the cursor back at its start)
int startPos = strmFileIn.tellg();
string strFirstLine;
getline(strmFileIn, strFirstLine);
strmFileIn.seekg(startPos, std::ios_base::beg);
//Tokenize strFirstLine into Array<int> tempArray
int numGroups = tempArray.size() - 1 //accounting for the row-header column, 1 - 13
//Create map (going to use java syntax, sorry)
Map<int,list<int>> myMap = new Map<int,list<int>>;
//Populate map with ints and empty lists (java again, sorry)
for (int i = 1; i <= numGroups; i++)
myMap.put(i, new List<int>);
//Iterate over lines in the file and appropriately populate the map's lists
while (fileIn != eof)
{
string fileInLine;
getline(strmFileIn, fileInLine);
//Tokenize fileInLine into Array<int> tempFileInArray
int intElemID = tempFileInArray[0];
//Remove element [0] from tempFileInArray (will be the row number, 1 - 13
//Iterate over remaining items in tempFileInArray, affect myMap where necessary
for (int i = 1; int i <= groupNum; i++)
if (tempFileInArray[i] != 0) //is not a strict truth-table, as any nonzero will be a truth
myMap.get[i].add(intElemID);
}
//Remove any entries in myMap with empty lists
//Kill strmFileIn for memory's sake
//End Parse
As you can see, my code is a broken mix of pseudocode and comparable Java I've already figured out. I just don't know how to turn this into C++; even with similar data structures, the syntax is a little daunting to someone with no experience. Is anyone here willing to help me out with it?
I really appreciate any insight.
Your code seems overly complicated, so lets do this one step at a time. Additionally, neither your code nor file format show how many bool columns should exist on each row, so I've ignored that part for this answer.
But first, a tip: In C++, the containers you care about 99.99% of the time are std::unordered_map, std::vector, and in very rare cases, std::map, boost::stable_vector and std::deque. In your case, you have rows with sequential indices, and the data for each row appears to be better stored as a vector of booleans. However, we'll do it your way, with the replacement of std::vector instead of std::list, and std::unordered_map instead of std::map.
This major data structures are mostly obvious:
std::unordered_map<int,std::vector<int>> myMap;
std::ifstream strmFileIn("input_file.txt");
Next your code reads in the first line, then ignores it entirely. I have no idea why, so I'll skip over that. Then, we parse out the lines one by one:
std::string full_current_line;
//for as long as we can read more lines, read them in
while(std::getline(strmFileIn, full_current_line)
{
//make the line into a stream so that we can parse data out
std::stringstream cur_line_stream(full_current_line);
//read in the line identifier
int identifier = 0;
cur_line_stream >> identifier;
//if that failed, abort.
if (!cur_line_stream)
{
//invalid identifer!
std::cerr << "identifier is invalid!\n"; //report
strmFileIn.setstate(std::ios::failbit); //failed to parse the data
break; //do not continue this loop
}
After that, we parse out the data for each row, which is surprisingly simple:
int column = 0;
int is_true = false;
//for each number remaining in the row...
while(cur_line_stream >> is_true)
{
//hooray we read a column!
++column;
if (is_true ==0)
{
//if it's zero, skip it
}
else if (is_true == 1)
{
//get the data for this column, and add this row's identifier
//myMap[column] will create a new empty entry if it didn't exist yet
//NOTE: This syntax only creates when used with map and unordered_map.
// This syntax does NOT create for vector and deque.
//once we have the vector, we push_back the new identifier into it.
myMap[column].push_back(identifier);
}
else
{
//invalid data!
std::cerr << is_true << " is invalid! found on row " << identifier << '\n';
cur_line_stream.setstate(std::ios::failbit); //failed to parse the data
strmFileIn.setstate(std::ios::failbit); //failed to parse the data
break; //do not continue this loop
}
}
}
If you know that groupNum contained the number of bools, you could replace that second while with something more like you already have:
for (int i = 1; int i <= groupNum; i++)
{
cur_line_stream >> is_true;
//if that failed, abort
if (!cur_line_stream)
{
//invalid data!
std::cerr << "data could not be read on row " << identifier << '\n';
cur_line_stream.setstate(std::ios::failbit); //failed to parse the data
strmFileIn.setstate(std::ios::failbit); //failed to parse the data
break; //do not continue this loop
}
else if (is_true == 0)
{
//if it's zero, skip it
}
etc etc etc
Work the other way. Code only in C++ (not in Java and don't think in Java), but start by parsing a small chunk of your syntax. First, code the lexer. Test it. Then code the parser, probably a recursive descent parser, and test it on short simple subelements of your language. Perhaps you'll need some small look-ahead (an easy task, use a std::list<Token>) Keep going up.
Start by formalizing, with pencil and paper, your input language. Could you for instance write a simple BNF grammar for it? (your question does not explain what is the input, it just gives an example)
In C++ parlance: to parse a map<int,list<int>> you certainly need to be able to parse int and list<int>. So write first the parsers for these.
As commented by Mooing Duck, your input language (which you did not define, just gave an example) seems simple enough to avoid most of this. But still, the idea is the same, think directly in C++ and start by reading a simple subpart of the input. Test your code. When that works, increase the part that is accepted. Repeat all this.
Here's a very simple solution that uses nothing but C++ and standard libraries. It just reads line by line and pulls each element out of the line with stream extraction using operator>>.
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <list>
int main(int argc, char* argv[])
{
// Parse command line
if( argc != 2 )
return 1;
std::fstream fin(argv[1]);
if( !fin.good() )
{
std::cerr << "Error opening file for reading: " << argv[1] << std::endl;
return 1;
}
// Skip first three lines
std::string line;
for( int i=0; i<3; ++i )
{
std::getline(fin, line);
}
// Read each line
std::map<int, std::list<int> > hits;
while( std::getline(fin, line) )
{
// Extract each element from the line
std::stringstream sstr(line);
// Read line number from first column
int linenum = 0;
sstr >> linenum;
// Interpret remaining columns as truth values
bool truth;
int col=1;
while( sstr >> truth )
{
// Store position in map if true
if( truth )
{
hits[col].push_back(linenum);
}
col++;
}
}
// Print results
std::map<int, std::list<int> >::const_iterator col_iter;
for( col_iter = hits.begin(); col_iter != hits.end(); ++col_iter )
{
std::cout << "[" << col_iter->first << ": {";
std::list<int>::const_iterator line_iter;
for( line_iter = col_iter->second.begin(); line_iter != col_iter->second.end(); ++line_iter )
{
std::cout << *line_iter << " ";
}
std::cout << "} ]" << std::endl;
}
return 0;
}

read in values and store in list in c++

i have a text file with data like the following:
name
weight
groupcode
name
weight
groupcode
name
weight
groupcode
now i want write the data of all persons into a output file till the maximum weight of 10000 kg is reached.
currently i have this:
void loadData(){
ifstream readFile( "inFile.txt" );
if( !readFile.is_open() )
{
cout << "Cannot open file" << endl;
}
else
{
cout << "Open file" << endl;
}
char row[30]; // max length of a value
while(readFile.getline (row, 50))
{
cout << row << endl;
// how can i store the data into a list and also calculating the total weight?
}
readFile.close();
}
i work with visual studio 2010 professional!
because i am a c++ beginner there could be is a better way! i am open for any idea's and suggestions
thanks in advance!
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <limits>
struct entry
{
entry()
: weight()
{ }
std::string name;
int weight; // kg
std::string group_code;
};
// content of data.txt
// (without leading space)
//
// John
// 80
// Wrestler
//
// Joe
// 75
// Cowboy
int main()
{
std::ifstream stream("data.txt");
if (stream)
{
std::vector<entry> entries;
const int limit_total_weight = 10000; // kg
int total_weight = 0; // kg
entry current;
while (std::getline(stream, current.name) &&
stream >> current.weight &&
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n') && // skip the rest of the line containing the weight
std::getline(stream, current.group_code))
{
entries.push_back(current);
total_weight += current.weight;
if (total_weight > limit_total_weight)
{
break;
}
// ignore empty line
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
else
{
std::cerr << "could not open the file" << std::endl;
}
}
Edit: Since you wannt to write the entries to a file, just stream out the entries instead of storing them in the vector. And of course you could overload the operator >> and operator << for the entry type.
Well here's a clue. Do you see the mismatch between your code and your problem description? In your problem description you have the data in groups of four lines, name, weight, groupcode, and a blank line. But in your code you only read one line each time round your loop, you should read four lines each time round your loop. So something like this
char name[30];
char weight[30];
char groupcode[30];
char blank[30];
while (readFile.getline (name, 30) &&
readFile.getline (weight, 30) &&
readFile.getline (groupcode, 30) &&
readFile.getline (blank, 30))
{
// now do something with name, weight and groupcode
}
Not perfect by a long way, but hopefully will get you started on the right track. Remember the structure of your code should match the structure of your problem description.
Have two file pointers, try reading input file and keep writing to o/p file. Meanwhile have a counter and keep incrementing with weight. When weight >= 10k, break the loop. By then you will have required data in o/p file.
Use this link for list of I/O APIs:
http://msdn.microsoft.com/en-us/library/aa364232(v=VS.85).aspx
If you want to struggle through things to build a working program on your own, read this. If you'd rather learn by example and study a strong example of C++ input/output, I'd definitely suggest poring over Simon's code.
First things first: You created a row buffer with 30 characters when you wrote, "char row[30];"
In the next line, you should change the readFile.getline(row, 50) call to readFile.getline(row, 30). Otherwise, it will try to read in 50 characters, and if someone has a name longer than 30, the memory past the buffer will become corrupted. So, that's a no-no. ;)
If you want to learn C++, I would strongly suggest that you use the standard library for I/O rather than the Microsoft-specific libraries that rplusg suggested. You're on the right track with ifstream and getline. If you want to learn pure C++, Simon has the right idea in his comment about switching out the character array for an std::string.
Anyway, john gave good advice about structuring your program around the problem description. As he said, you will want to read four lines with every iteration of the loop. When you read the weight line, you will want to find a way to get numerical output from it (if you're sticking with the character array, try http://www.cplusplus.com/reference/clibrary/cstdlib/atoi/, or try http://www.cplusplus.com/reference/clibrary/cstdlib/atof/ for non-whole numbers). Then you can add that to a running weight total. Each iteration, output data to a file as required, and once your weight total >= 10000, that's when you know to break out of the loop.
However, you might not want to use getline inside of your while condition at all: Since you have to use getline four times each loop iteration, you would either have to use something similar to Simon's code or store your results in four separate buffers if you did it that way (otherwise, you won't have time to read the weight and print out the line before the next line is read in!).
Instead, you can also structure the loop to be while(total <= 10000) or something similar. In that case, you can use four sets of if(readFile.getline(row, 30)) inside of the loop, and you'll be able to read in the weight and print things out in between each set. The loop will end automatically after the iteration that pushes the total weight over 10000...but you should also break out of it if you reach the end of the file, or you'll be stuck in a loop for all eternity. :p
Good luck!