File handlling in C++: updating a line - c++

I am writing program for Library Management. I have a file Student.dat which have four columns. Initially when no book is issued it looks like this.
---------------Students List ----------------
Roll No. Name Book Issued Issued Book No.
001 Abhi 0 No
002 Ashu 0 No
After issuing book to '001'.
---------------Students List ----------------
Roll No. Name Book Issued Issued Book No.
001 Abhi 1 1001
02 Ashu 0 No
The roll number of second student becomes '02'.
This is complete issue function in library.cpp
void Library::book_issue()
{
//Some code
fp.open("Students.dat", std::ios::in | std::ios::out);
fp1.open("Books.dat", std::ios::in | std::ios::out);
//////////////////////////////////////////////////////////////////////
int oldPos = fp.tellg();
while (std::getline(fp, line) && !found_stu)
{
std::stringstream ss(line);
ss >> roll_n >> s_name >> tkn >> issued_b_num;
////////////
std::getline(ss, line);
if (boost::iequals(roll_n, r_num))
{
found_stu = true;
if (tkn == 0)
{
std::cout << "Enter Book No. : ";
std::getline(std::cin, b_num);
while (fp1 >> book_n >> b_name >> a_name && !found_book)
{
if (boost::iequals(book_n, b_num))
{
Book::show_book(book_n, b_name, a_name);
found_book = true;
tkn = 1;
Student::reset_issued_book_num();
issued_b_num = book_n;
//////////////////////////////////////////////////////////////////
fp.seekg(oldPos);
fp << roll_n << " " << s_name << " " << tkn << " " << issued_b_num << '\n';
std::cout << "Book Issued Successfully\n";
break;
}
}
if (!found_book)
{
std::cerr << "Book does not exist\n";
}
}
}
}
if (!found_stu)
{
std::cout << "Student record does not exist\n";
}
fp.close();
fp1.close();
}
I want to know whether I have used oldPos variable correctly?
Edit:
After assigning length of Issued Book No. as length of book number, I get repeated record.
---------------Students List ----------------
Roll No. Name Book Issued Issued Book No.
001 Abhi 1 1001
001 Abhi 1 1001
002 Ashu 0 No

The problem is that you overwrite the file that you read. So if one line would become longer, you'd overwrite characters of the next line(s).
As 002 becomes 02 and not 2, I'll assume that No in the file is followed by a whitespace. So if I use to show in a visible manner the LineFeed, the following content of your file:
...NO <LF>002...
will be overwriten with:
...1001<LF>02...
^ (end of the write, remaining chars unchanged)
So the 3 chars No are overwritten with 100, the LineFeed is overwritten with 1 and the 0 is overwritten with the new LineFeed.
If you want to write in-place like you try here, you must ensure that the size of each line remains fixed in all circumstances. So "No" should be followed by the number of space needed to match the length of a book number.
Other remarks
It's not the cause of the error, but tellg() returns a std::streampos, which can be much larger than an int. So I'd recommend to prefer:
auto oldPos = fp.tellg(); // here you're sure it's the right type
Note also that tellg()/seekg() are meant for input stream and tellp()/seekp()for output streams. Fortunately, for bidirectional file streams, there is only one position for reading and writing. But for other kind of bidirectional strings, this is not guaranteed (see this question).
Finally, if the goal of your repositionning is to overwrite the last line, read (and found) you should update it from time to time.

Related

How to Read Numerical, Minute and Second Values from File, While Writing Values *Exactly As Given* To Other File

First off, is it possible?
To extrapolate the question: I'd like to read some integers from a file, which is relatively simple for me.
However, when I read these values, which are formatted: "123 17 24 55 04 30 09" for example, my written output shows "123 17 24 55 4 30 9", where the "0" is absent before the 4, and the 9. This bothers me from a formatting aspect. How do I retain the 0 which is infront of the 4, and 9, in my output?
My current code is attached
int main() {
ifstream inf;
ofstream of;
float timeSmin, timeSsec, timeBmin, timeBsec=01.00, timeRmin=01, timeRsec=01.00;
int ID, totaltime;
inf.open ("triath.txt");
of.open ("output.txt");
if (!inf.is_open()){
cout << "Triath.txt cannot be opened, error..." << endl;
}
inf >> ID >> timeSmin >> timeSsec >> timeBmin >> timeBsec >> timeRmin >> timeRsec; //writing values to input object.
of << ID << " " << timeSmin << " " << timeSsec << " " << timeBmin << " " << timeBsec << " " << timeRmin << " " << timeRsec;
inf.close();
of.close();
return 0;
} ```
I would read the file in as a string and parse the string. That gives you the original string to use for output.
You'll need a way of splitting your input string into tokens. If you can't figure that out, you can do a google for "c++ split string by spaces" and get a bunch of good hits.
You'll need to then convert substrings into integers. Again, a google for "c++ convert string to integer" will provide some clues.
Then you just output the original string as is, and it's identical to what you originally read.

A 'stack overflow' error returns upon any array size I enter above 36603. How can I make a string capable of capturing my entire .txt file?

I need to create a string capable of holding the entire book 'The Hunger Games' which comes out to around 100500 words. My code can capture samples of the txt, but anytime I exceed a string size of 36603(tested), I receive a 'stack overflow' error.
I can successfully capture anything below 36603 elements and can output them perfectly.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
int i;
char set[100];
string fullFile[100000]; // this will not execute if set to over 36603
ifstream myfile("HungerGames.txt");
if (myfile.is_open())
{
// saves 'i limiter' words from the .txt to fullFile
for (i = 0; i < 100000; i++) {
//each word is saparated by a space
myfile.getline(set, 100, ' ');
fullFile[i] = set;
}
myfile.close();
}
else cout << "Unable to open file";
//prints 'i limiter' words to window
for (i = 0; i < 100000; ++i) {
cout << fullFile[i] << ' ';
}
What is causing the 'stack overflow' and how can I successfully capture the txt? I will later be doing a word counter and word frequency counter, so I need it in "word per element" form.
There's a limit on how much stack is used in a function; Use std::vector instead.
More here and here. The default in Visual studio is 1MB (more info here) and you can change it with /F, but this is a bad idea generally.
My system is Lubuntu 18.04, with g++ 7.3. The following snippet shows some "implementation details" of my system, and how to report them on yours. It would help you to understand what your system provides ...
void foo1()
{
int i; // Lubuntu
cout << "\n sizeof(i) " << sizeof(i) << endl; // 4 bytes
char c1[100];
cout << "\n sizeof(c1) " << sizeof(c1) << endl; // 100 bytes
string s1; // empty string
cout << "\n s1.size() " << s1.size() // 0 bytes
<< " sizeof(s1) " << sizeof(s1) << endl; // 32 bytes
s1 = "1234567890"; // now has 10 chars
cout << "\n s1.size() " << s1.size() // 10 bytes
<< " sizeof(s1) " << sizeof(s1) << endl; // 32 bytes
string fullFile[100000]; // this is an array of 100,000 strings
cout << "\n sizeof(fullFile) " // total is vvvvvvvvv
<< sops.digiComma(sizeof(fullFile)) << endl; // 3,200,000 bytes
uint64_t totalChars = 0;
for( auto ff : fullFile ) totalChars += ff.size();
cout << "\n total chars in all strings " << totalChars << endl;
}
What is causing the 'stack overflow' and how can I successfully
capture the txt?
The fullFile array is an unfortunate choice ... because each std::string, even when empty, consumes 32 bytes of automatic memory (~stack), for a total of 3,200,000 bytes, and this is with no data in the strings! This will stack overflow your system when the stack is smaller than the automatic var space.
On Lubuntu the default automatic-memory size (lately) is 10 M Bytes, so not a problem for me. But you will have to check on what your version of your target os defaults to. I think Windows defaults down near 1 M Byte. (Sorry, I don't know how to check Windows automatic-memory size.)
How can I make a string capable of capturing my entire .txt file.
The answer is -- you don't need to make your own. (unless you have some unstated requirement)
Also, you really should look at en.cppreference.com/w/cpp/string/basic_string/append".
In my 1st snippet above, you should take notice that the sizeof(string) reports 32 bytes, regardless of how many chars are in it.
Think on that a while ... if you put 1000 chars into a string, where do they go? The objects stays at 32 bytes! You might guess or read that the string object handles memory management on your behalf, and puts all characters into dynamic-memory (heap).
On my system, heap is about 4 G bytes. That's a lot more than stack.
In summary, every single std::string expands auto-magically, using heap, so if your text input will fit in heap, it will fit into '1 std::string'.
While browsing around in the cppreference, check out the 'string::reserve()' command.
Conclusion:
Any std::string you declare can auto-magically 'grow' to support your need, and will thus hold the entire text (if it will fit in memory).
Operationally, you simply get a line of text from the file, then append it to the single string, until the entire file is contained. You only need the one array, which std::string provides.
With this new idea ... I suggest you change fullFile from an array to a string.
string fullFile; // file will expand to handle append actions
// to the limit of available heap.
// open file ... check status
do {
myfile.getline(line); // fetch line of text up thru the line feed
// Note that getline does not put the \n into 'line'
// there are file state checks that should be done (perhaps here?)
// tbd - line += '\n';
// you may need the line feed in your fullFile string?
fullFile += line; // append the line
} while (!myfile.eof); // check for eof
// ... other file cleanup.
foo1() output on Lubuntu 18.04, g++ v7.3
sizeof(i) 4
sizeof(c1) 100
s1.size() 0 sizeof(s1) 32
s1.size() 10 sizeof(s1) 32
sizeof(fullFile) 3,200,000
total chars in all strings 0
Example slurp() :
string slurp(ifstream& sIn)
{
stringstream ss;
ss << sIn.rdbuf();
dtbAssert(!sIn.bad());
if(sIn.bad())
throw "\n DTB::slurp(sIn) 'ss << sIn.rdbuf()' is bad";
ss.clear(); // clear flags
return ss.str();
}

Deleting from a file C++

Alright so Here is the code I have currently for my delete function.
void classSchedule::deleteEntry(classSchedule schedule[], int numElems)
{
string entryToDelete;
cout << endl << "Enter the Teacher's Last Name of the Entry you want to delete: ";
cin >> entryToDelete;
int i, recordToDelete = -1;
for (i = 0; i < numElems; i++)
{
if (schedule[i].teacherLastName == entryToDelete)
recordToDelete = i;
}
if (recordToDelete != -1)
{
}
}
It essentially is like this: I have an array of classes that is called schedule[] each object has the following members: class department, class number, credit hours, teacher last name, teacher first name, room number, and current gpa. I started my function by asking them the teachers last name they want to delete, because all the rest could repeat. I searched my object for that last name.
I want to know how to delete the whole instance of that object from a file.
Is it possible for me to just overwrite it with the objects ahead or behind it.
i.e. they want to delete schedule[4] out of 10 instances could i just overwrite [4] with [5], [5] with [6] and so on?
Edit:
Here is the file that is being read wrote to(classes.txt):
ENG 112 3 Tetlof S S062 3.1
CST 280 3 Klingler T K114 3.4
LWA 220 2 Wesolak J M121 2.1
POL 103 4 Fortin B J122 4.1
ENG 111 2 Harood J K131 3.1
Edit2:
void classSchedule::outputToFile(classSchedule schedule[], int& numElems)
{
ofstream fileOut;
fileOut.open("classes.txt", ios::out | ios::app);
fileOut << schedule[numElems - 1].classDepartment
<< " " << schedule[numElems - 1].creditHours
<< " " << schedule[numElems - 1].teacherLastName
<< " " << schedule[numElems - 1].teacherFirstName
<< " " << schedule[numElems - 1].roomWingAndNumber
<< " " << schedule[numElems - 1].currentGPA;
fileOut.close();
}
You have three options: 1) Output the modified data to a new file; or 2) Mark records as deleted, but available for reuse; or 3) Move data "up" to the unused slots (like an array).
Maybe you should be using a database instead.
Many applications write the original and modified data to a new file. Data that is to be deleted is not written to the file. This is the simplest solution.
If your file has fixed sized records, you could mark the deleted record as available so it can be used again. This may require adding a flag to the record that indicates whether it is dead or alive.
An ugly method is to overwrite the deleted data with data that is forward of it. For example if I have a file containing 3 sentences and I delete sentence 2, I will read sentence 3 and write it to the position where sentence 2 started.

Order files with MergeSort in c++

I'm triying to implement my own MergeSort, but I've got some problems, see if anyone can help me a little.
I have a big file with some info separeted with coma (Name,city,mail,telf). I would like to apply mergesort to order it, because I supose that the client computer wont have as much memory to do it in one try.
So, I split it into files of MAX_CUSTOMERS lines, and order them individually, all correct until here, but when I want to get the first two files and order them, I've got all the problems, I got repeated, ones and others dissapear, here's my code:
void MergeSort(string file1Name, string file2Name,string name){
printf("Enter MERGE SORT %s AND %s\n",file1Name.c_str(),file2Name.c_str());
string temp;
string fileName;
string lineFile1, lineFile2;
bool endFil1 = false, endFil2 = false;
int numCust1 = 0;
int numCust2 = 0;
int x1 = 0, x2 = 0;
ifstream file1;
file1.open(file1Name.c_str());
ifstream file2;
file2.open(file2Name.c_str());
ofstream mergeFile;
fileName = "customers_" +name +".txt";
cout << "Result file " << fileName << endl;
mergeFile.open("temp.txt");
getline(file1,lineFile1);
getline(file2,lineFile2);
while(!endFil1 && !endFil2){
if(CompareTelf(lineFile1,lineFile2)==1){
mergeFile << lineFile1 << endl;
if(!getline(file1,lineFile1)){
cout << lineFile1 << endl;
cout << "1st file end" << endl;
endFil1 = true;
}
}else{
mergeFile << lineFile2 << endl;
if(!getline(file2,lineFile2)){
cout << lineFile2 << endl;
cout << "2nd file end" << endl;
endFil2 = true;
}
}
}
if(endFil1){
//mergeFile << lineFile2 << endl;
while(getline(file2,lineFile2)){
mergeFile << lineFile2 << endl;
}
}else{
//mergeFile << lineFile1 << endl;
while(getline(file1,lineFile1)){
mergeFile << lineFile1 << endl;
}
}
file1.close();
file2.close();
mergeFile.close();
rename("temp.txt",fileName.c_str());
return;
}
Customer SplitLine(string line){
string splitLine;
string temp;
Customer cust;
int actProp = 0;
int number;
istringstream readLineStream(line); //convert String readLine to Stream readLine
while(getline(readLineStream,splitLine,',')){
if (actProp == 0)cust.name = splitLine;
else if (actProp == 1)cust.city = splitLine;
else if (actProp == 2)cust.mail = splitLine;
else if (actProp == 3)cust.telf = atoi(splitLine.c_str());
actProp++;
}
//printf("Customer read: %s, %s, %s, %i\n",cust.name.c_str(), cust.city.c_str(), cust.mail.c_str(), cust.telf);
return cust;
}
int CompareTelf(string str1, string str2){
Customer c1 = SplitLine(str1);
Customer c2 = SplitLine(str2);
if(c1.telf<c2.telf)return 1; //return 1 if 1st string its more important than second, otherwise, return -1
else return -1;
}
struct Customer{
string name;
string city;
string mail;
long telf;
};
If have some question about the code, just say it! I tried to use varNames as descriptive as possible!
Thanks a lot.
Your code seems quite good, but it has several flaws and one important omission.
One of the minor flaws is lack of initialization of Customer structure - you didn't provide a constructor to the struct, and do no explicit initialization of the cust variable. Hopefully string members are properly initialized by the string class constructor, but long telf may get any initial value.
Another one is lack of format checking in splitting an input line. Are you sure that every input line has same format? If there are lines with too many commas (say, comma inside a name) then the loop may incorrectly try to assign 'email' data to 'telf' member...
OTOH if there is too few commas, the 'telf' member may remain uninitialized, with a random initial value...
Together with the first one this flaw may lead to incorrect order of output data.
Similar problems arise when you use atoi function: it returns int but your variable is long. I suppose you have chosen long type because of the expected range of values - if so, converting input data to int may truncate significant part of data! I'm not sure what atoi does in that case, it may either return the result of converting some initial part of the input string or just return zero. Both values are wrong and lead to incorrect sorting, so you better use atol instead.
Next issue is reading first line from both input files. You don't check if getline() succeeded. If an input file is empty, the corresponding lineFile_num string will be empty, but endFil_num will not reflect that - it will still be false. So you again go into comparing invalid data.
Finally the main problem. Assume the file1 contents is 'greater than' (that is: goes after) the whole file2. Then the first line stored in lineFile1 results in CompareTelf() returning -1 all the time. the main loop copies the whole file2 into the output, and...? And the final while() loop starts with getline(file1,lineFile1) thus discarding the first line of file1!
Similar result happens with files consisting of records (A,C) and (B), to be merged as (A,B,C): first A and B are read in, then A is saved and C is read in, then B is saved and end of file 2 detected. Then while(getline(...)) cancels C in memory and finds end of file 1, which terminates the loop. Record C gets lost.
Generally, when the main merging loop while(!endFil1 && !endFil2) exhausts one of files, the first unsaved line of the other file gets discarded. To avoid this you need to store the result of the first read:
endFil1 = ! getline(file1,lineFile1);
endFil2 = ! getline(file2,lineFile2);
then, after the main loop, start copying the input file's tail with the unsaved line:
while(!endFil1) {
mergeFile << lineFile1 << endl;
endFil1 = !getline(file1,lineFile1);
}
while(!endFil2) {
mergeFile << lineFile2 << endl;
endFil2 = !getline(file2,lineFile2);
}

I'm Having Trouble Skipping Certain Characters from an Input Text File (C++)

(I apologize that this is so low level compared to most of the questions I have seen on this website, but I have run out of ideas and I do not know who else to ask.)
I am working on a school project that requires me to read basketball statistics from a file named in06.txt. The file in06.txt looks exactly as follows:
5
P 17 24 9 31 28
R 4 5 1 10 7
A 9 2 3 6 8
S 3 4 0 5 4
I am required to read and store the first number, 5, into a variable called "games." From there, I must read the numbers from the second line and determine the high, the low, and the average. I must do the same thing for lines 3, 4, and 5. (FYI, the letters P, R, A, and S are there to indicate "Points," "Rebounds," "Assists," and "Steals.")
Since I only have been learning about programming for a few weeks, I do not want to overwhelm myself by jumping right into dealing with every aspect of the project. So, I am first working on determining the average from each line. My plan is to keep a running total of each line and then divide the running total by the number of games, which is 5 in this case.
This is my code:
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
int main()
{
int games;
int points_high, points_low, points_total;
int rebounds_high, rebounds_low, rebounds_total;
int assists_high, assists_low, assists_total;
int steals_high, steals_low, steals_total;
double points_average, rebounds_average, assists_average, steals_average;
ifstream fin;
ofstream fout;
fin.open("in06.txt");
if( fin.fail() ) {
cout << "\nInput file opening failed.\n";
exit(1);
}
else
cout << "\nInput file was read successfully.\n";
int tempint1, tempint2, tempint3, tempint4;
char tempchar;
fin >> games;
fin.get(tempchar); // Takes the endl; from the text file.
fin.get(tempchar); // Takes the character P from the text file.
while( fin >> tempint1 ) {
points_total += tempint1;
}
fin.get(tempchar); // Takes the endl; from the text file.
fin.get(tempchar); // Takes the character R from the text file.
while( fin >> tempint2 ) {
rebounds_total += tempint2;
}
fin.get(tempchar); // Takes the endl; from the text file.
fin.get(tempchar); // Takes the character A from the text file.
while( fin >> tempint3 ) {
assists_total += tempint3;
}
fin.get(tempchar); // Takes the endl; from the text file.
fin.get(tempchar); // Takes the character S from the text file.
while( fin >> tempint4 ) {
steals_total += tempint4;
}
cout << "The total number of games is " << games << endl;
cout << "The value of total points is " << points_total << endl;
cout << "The value of total rebounds is " << rebounds_total << endl;
cout << "The value of total assists is " << assists_total << endl;
cout << "The value of total steals is " << steals_total << endl;
return 0;
}
And this is the (incorrect) output:
Input file was read successfully.
The total number of games is 5
The value of total points is 111
The value of total rebounds is 134522076
The value of total assists is 134515888
The value of total steals is 673677934
I have been reading about file input in my textbook for hours, hoping that I will find something that will indicate why my program is outputting the incorrect values. However, I have found nothing. I have also researched similar problems on this forum as well as other forums, but the solutions use methods that I have not yet learned about and thus, my teacher would not allow them in my project code. Some of the methods I saw were arrays and the getline function. We have not yet learned about either.
Note: My teacher does not want us to store every integer from the input file. He wants us to open the file a single time and store the number of games, and then use loops and if statements for determining the high, average, and low numbers from each line.
If anyone could help me out, I would GREATLY appreciate it!
Thanks!
You have all these variables declared:
int games;
int points_high, points_low, points_total;
int rebounds_high, rebounds_low, rebounds_total;
int assists_high, assists_low, assists_total;
int steals_high, steals_low, steals_total;
double points_average, rebounds_average, assists_average, steals_average;
And then you increment them:
points_total += tempint1;
Those variables were never initialzed to a known value (0), so they have garbage in them. You need to initialize them.
Besides what OldProgrammer said, you've approached the reading of integers incorrectly. A loop like this
while( fin >> tempint2 ) {
rebounds_total += tempint2;
}
will stop when an error occurs. That is, either it reaches EOF or the extraction encounters data that cannot be formatted as an integer - or in other words, good() returns false. It does not, as you seem to think, stop reading at the end of a line. Once an error flag is set, all further extractions will fail until you clear the flags. In your case, a loop starts reading after P, extracts five intergers, but then it encounters the R from the next line and errors out.
Change this to a loop that reads a fixed number of integers or alternatively, read a whole line using std::getline into a std::string, put it into a std::stringstream and read from there.
In any case, learn to write robust code. Check for success of extractions and count how many elements you get.
An example of a loop that reads at most 5 integers:
int i;
int counter = 0;
while (counter < 5 && file >> i) {
++counter;
// do something with i
}
if (counter < 5) {
// hm, got less than 5 ints...
}