Overwrite a line with ofstream C++ - c++

I am doing a little game and I am saving the player details in a txt file.
Example of that txt file:
Eric 13 8 10 10 30 10 10 50 0 0 0 0
William 1 0 10 30 30 10 10 50 0 0 0 0
John 1 0 10 30 30 10 10 50 0 0 0 0
This is what I had in mind: when the player chooses to save the game while playing, the save_game function should check if there is already any saved data. If there is, instead of appending the data to the end of the txt, it should overwrite that specific line.
Here is my current function:
// SAVE GAME
void save_game(Player player)
{
ofstream coutfile (SaveDestiny, ios::app);
if (coutfile.is_open()) // if it opens correctly
{
// Now checking if the name already exists
string imported_name;
ifstream cinfile (SaveDestiny); // opens file that contains the saved games
cinfile >> imported_name; // getting first element of file
bool j = 0; // j = 0 while the strings don't match. j = 1 when the string was found
while (cinfile >> imported_name) // while the end of file is not reached
{
if (player.name.compare(imported_name) == 0) // if the strings are the same, overwrite data
{
j = 1;
coutfile << " \r" << endl;
break;
}
else // if the strings are different, keep reading
{
cinfile >> imported_name;
}
}
// Continuing...
coutfile << player.name << " " << player.level << " " << player.exp << " " << player.max_exp << " "
<< player.hp << " " << player.max_hp << " " << player.mp << " " << player.max_mp << " "
<< player.gold << " " << player.weapon << " " << player.shield << " " << player.heal_spell << " "
<< player.attack_spell << endl;
}
else
{
ofstream coutfile (SaveDestiny, ios::app);
coutfile << "test";
cout << "Unable to open file";
cin.get();
}
draw_rectangle(37,8,72,14,15); // white limits
draw_rectangle(39,9,70,13,9); // blue background
cor(9,15);
gotoxy(50,10);
cout << "GAME SAVED!";
gotoxy(41,12);
cor(9,14);
cout << "Press <Enter> to continue... ";
cin.get();
}

On most modern filesystems files are not "line-based" (or "record-based") they are character-based so you can't "overwrite a line". The old line might be 20 characters long and the new one would be 24 characters, in which case it would overwrite the old line and the first 4 characters of the next line. To make this work you would have to "push" everything after the line later in the file, which isn't possible with C++ (or C) IO facilities.
One option would be to write all lines with a fixed length, say 50 characters, so that overwriting the 3rd line involves replacing characters 100 to 149, even if the line only actually needs 24 characters.
Another option would be to keep the file in memory in a record-based form and write out the entire file every time you change it (or at least write out the new line and all lines that come after it)

Ok I've managed to get around the problem and now it's working brilliantly! :D
First, the function checks if the player name already is on the txt. I created a enable variable j. When j=1, the name exists and the data needs to be overwritten! When j=0, the function will append the data to the txt right away.
Ok, let's say j=1. The function determines the number of lines in txt. It then creates a vector with two vectors inside: the name, and the game variables.
After that, the function deletes the previouscontent of txt file. And writes the content of the vector to the txt, except the data that needs to be overwritten (it will skip writing that part to the txt), because at the end of the function, that new data will be written. :D I hope I made myself clear enough. Sorry if someone doesn't understand what I wrote...
Here is my new save_game function:
// SAVE GAME
void save_game(Player player)
{
ofstream coutfile (SaveDestiny, ios::app);
if (coutfile.is_open()) // if it opens correctly
{
string imported_name;
ifstream cinfile (SaveDestiny); // opens file that contains the saved games
bool j = 0;
// Now checking if the name already exists
while (cinfile >> imported_name) // while the end of file is not reached
{
if (player.name.compare(imported_name) == 0) // if the strings are the same, overwrite data
{
j = 1; // enable overwrite
break;
}
// if the strings are different, keep reading
}
// at this point: j = 0 to append to end. j = 1 to overwrite.
// Overwriting data
if (j == 1)
{
ifstream cinfile (SaveDestiny);
// now determining the size of the vector (number of lines in txt)
int line_numbers = 0;
string line;
while (getline(cinfile, line))
{
line_numbers++;
}
cinfile.close(); // closing
ifstream cinfile2 (SaveDestiny); // reopening to read from the beginning
// now creating the vector with the saves
vector<vector<string>> temp_saves(line_numbers, vector<string>(2));
string name2;
string values;
for (unsigned int x = 0; x < temp_saves.size(); x++)
{
cinfile2 >> name2;
getline(cinfile2, values);
temp_saves[x][0] = name2;
temp_saves[x][1] = values;
}
coutfile.close(); // closing output file
ofstream coutfile2 (SaveDestiny); // reopening in overwrite mode
// delete all saves.txt, copying vector content to txt (except the one we want to overwrite)
for (unsigned int x = 0; x < temp_saves.size(); x++)
{
if ( temp_saves[x][0].compare(player.name) != 0)
{
coutfile2 << temp_saves[x][0] << temp_saves[x][1] << endl;
}
}
coutfile2.close(); // closing output file
}
// Appending new data...
ofstream coutfile3 (SaveDestiny, ios::app); // reopening in append mode
coutfile3 << player.name << " " << player.level << " " << player.exp << " " << player.max_exp << " "
<< player.hp << " " << player.max_hp << " " << player.mp << " " << player.max_mp << " "
<< player.gold << " " << player.weapon << " " << player.shield << " " << player.heal_spell << " "
<< player.attack_spell << endl;
}
else
{
ofstream coutfile (SaveDestiny, ios::app);
cout << "Unable to open file";
cin.get();
}
draw_rectangle(37,8,72,14,15); // white limits
draw_rectangle(39,9,70,13,9); // blue background
cor(9,15);
gotoxy(50,10);
cout << "GAME SAVED!";
gotoxy(41,12);
cor(9,14);
cout << "Press <Enter> to continue... ";
cin.get();
}

Related

Trouble reading data from Parquet File

I am attempting to read parquet data from a binary stream (via API posts). For example: I have a rather large parquet file on the other side of an REST API and need to fetch parts of the file. I have been attempting to follow file spec here: https://github.com/apache/parquet-format however, the pattern seems to be failing (or I am misunderstanding part).
For my test, I have moved a parquet file onto my local system and am reading in binary data from the file using ifstream. My steps are as follows:
Read in magic number from header
Read in magic number from footer
Read in FileMetaData length
Read in FileMetaData (from bottom of file)
Convert stream to FileMetaData Type using:
std::shared_ptr<parquet::FileMetaData> _metadata = parquet::FileMetaData::Make(metadataBuffer.data(), &metadataLength);
Read in RowGroup(0) and RowGroup(1) file_offset and total_byte_size from the FileMetaData like this:
_metadata->RowGroup(x)->file_offset();
_metadata->RowGroup(x)->total_byte_size();
After storing this data, I proceed to read in each RowGroup from the file using ifstream again. My start position is the file_offset from the beginning of the file.
Once my RowGroup data is read in to a vector of objects, I attempt to convert the buffered data into RowGroupMetaData
std::shared_ptr<parquet::RowGroupMetaData> _rowGroupMetaData = parquet::RowGroupMetaData::Make(rowGroupData[x].rowGroupBuffer.data(), rowGroupData[x].schema);
This is where I get stuck. When I try to access parts of the _rowGroupMetaData, I am getting junk back. It seems I must be skipping a step or overlooking part of the file spec.
I noticed that there is data between the magic number PAR1 at the top of the file an the file offset of RowGroup(0). the magic number is 4 characters long but the RowGroup(0) file_offset = 113. I am not sure what the data between 4-113 is and I cannot find information on it in the spec.
My parquet file is rather simple. 2 RowGroups with 2 columns. Total of 5 rows across both RowGroups.
Code:
ifstream inFile("parquet-arrow-example.parquet", std::ofstream::binary | std::ios::ate);
std::streamsize fileSize = inFile.tellg();
inFile.seekg(0, std::ios::beg);
std::vector<char> headBuffer;
std::vector<char> tailBuffer;
std::vector<uint8_t> metadataBuffer;
headBuffer.resize(4);
tailBuffer.resize(4);
struct RowGroupData {
int groupId;
int64_t byteLength;
int64_t offset;
const parquet::SchemaDescriptor* schema;
vector<uint8_t> rowGroupBuffer;
};
uint32_t metadataLength = 0;
string header;
string footer;
//Header
inFile.read((char*)&headBuffer[0], headBuffer.size()); //PAR1
header = string(headBuffer.begin(), headBuffer.end());
cout << header << endl;
//Footer
inFile.seekg(-4, std::ios::end);
inFile.read((char*)&tailBuffer[0], tailBuffer.size()); //PAR1
footer = string(tailBuffer.begin(), tailBuffer.end());
cout << footer << endl;
//Metadata Size
inFile.seekg(-8, std::ios::end);
inFile.read((char*)&metadataLength, 4);
cout << "Metadata Length: " << metadataLength << endl;
int len = -8 - metadataLength;
//Get MetaData
inFile.seekg(len, std::ios::end);
metadataBuffer.resize(metadataLength);
inFile.read((char*)&metadataBuffer[0], metadataBuffer.size());
cout << string(metadataBuffer.begin(), metadataBuffer.end()) << endl;
std::shared_ptr<parquet::FileMetaData> _metadata = parquet::FileMetaData::Make(metadataBuffer.data(), &metadataLength);
cout << "Num Rows: " << _metadata->num_rows() << endl;
cout << "Num Columns: " << _metadata->num_columns() << endl;
cout << "Num RowGroups: " << _metadata->num_row_groups() << endl;
vector<RowGroupData> rowGroupData;
//int seeqPos = 4;
for (int x = 0; x < _metadata->num_row_groups(); x++) {
cout << "RowGroup " << x << " Byte Size: " << _metadata->RowGroup(x)->total_byte_size() << endl;
cout << "RowGroup " << x << " File Offset: " << _metadata->RowGroup(x)->file_offset() << endl;
cout << "RowGroup " << x << " Column 0 File Offset: " << _metadata->RowGroup(x)->ColumnChunk(0)->file_offset() << endl;
cout << "RowGroup " << x << " Column 0 Byte Size: " << _metadata->RowGroup(x)->ColumnChunk(0)->total_compressed_size() << endl;
cout << "RowGroup " << x << " Column 1 File Offset: " << _metadata->RowGroup(x)->ColumnChunk(1)->file_offset() << endl;
cout << "RowGroup " << x << " Column 1 Byte Size: " << _metadata->RowGroup(x)->ColumnChunk(1)->total_compressed_size() << endl;
RowGroupData rgData;
rgData.groupId = x;
rgData.byteLength = _metadata->RowGroup(x)->total_byte_size();
rgData.offset = _metadata->RowGroup(x)->file_offset();
rgData.schema = _metadata->RowGroup(x)->schema();
rgData.rowGroupBuffer.resize(rgData.byteLength);
//Store rowGroup Length
//Store rowGroup Data
inFile.seekg(rgData.offset, std::ios::beg);
inFile.read((char*)&rgData.rowGroupBuffer[0], rgData.rowGroupBuffer.size());
rowGroupData.push_back(rgData);
//seeqPos = seeqPos + rgData.byteLength;
}
cout << endl;
for (int x = 0; x < rowGroupData.size(); x++) {
vector<uint8_t> rgBuffer;
//rgBuffer = rowGroupData[x].rowGroupBuffer;
cout << "RowGroupId: " << rowGroupData[x].groupId << endl;
cout << "RowGroupData: " << string(rowGroupData[x].rowGroupBuffer.begin(), rowGroupData[x].rowGroupBuffer.end()) << endl;
std::shared_ptr<parquet::RowGroupMetaData> _rowGroupMetaData = parquet::RowGroupMetaData::Make(rowGroupData[x].rowGroupBuffer.data(), rowGroupData[x].schema);
cout << "RowGroup Rows: " << _rowGroupMetaData->num_rows() << endl;
cout << "Byte Size: " << _rowGroupMetaData->total_byte_size() << endl;
}
The data between the file header and the file_offset is the column_chunk metadata for the first column.
The parquet spec is a little confusing because there are two different file offsets. The one on the RowGroup is an offset to the first page of data in the row group. And the column chunk file_offset which points to the column chunks metadata.
To my knowledge the first offset is mostly used for splitting files, I think most other readers use the latter offset for parsing columns.
Also note that in C++ at least file_offset was being written out incorrectly prior to the release of Arrow 6.0 (it pointed to the same byte offset as that the column offset chunk did).
Last, parquet is a non-trivial format and it is easy to have subtle bugs, I'd strongly recommend trying to use a standard implementation which has been battle tested rather then creating your own. If something is missing from the API it might be simpler to contribute it to an existing implementation instead of trying to build everything from scratch.

C++ Writing a vector of objects to file

I have a vector of objects with quite a few variables (name, type, length etc) which I am trying to write to file.
vector <Boat> berths;
void Boat::write_boats()
{
ofstream file("records_file.txt");
for (Boat b : berths)
{
file << owner_name << "; " << boat_name << "; " << type << "; " << length << "; " << draft << '\n';
}
file.close();
}
void save_records()
{
for (unsigned int i = 1; i < berths.size(); i++)
{
berths[i].write_boats();
}
}
I call the save_records() function with a menu option that ends the application.
The output i get is:
1) If i register a boat object, close the app and go in the text file, I can see the object written twice.
2) If i register 2 objects and I go in the text file, only the last (second) object has been written to file, and it shows 3 times.
Now my questions are:
What causes the double output?
Why is only the last object written to file? I thought the loop would fix that but it didn't
One problem I can spot: "i = 1" in the loop should be "i = 0", because array indexes start from 0. The second: you iterate 'berths' array, so you will get N * N boats saved, if you have N boats in 'berths'.
The simple solution would be
void save_all()
{
ofstream file("records_file.txt");
for (Boat b : berths)
{
file << b.owner_name << "; " << b.boat_name << "; " << b.type << "; " << b.length << "; " << b.draft << '\n';
}
}
If you have to make 'owner_name', 'type' and the rest of the fields as private, then you would have to declare
void Boat::save(std::ofstream& f) const
{
file << owner_name << "; " << boat_name << "; " << type << "; " << length << "; " << draft << '\n';
}
and modify 'save_all' to
void save_all()
{
ofstream file("records_file.txt");
for (const Boat& b: berths)
b.save(f);
}
Every time ofstream file("records_file.txt"); is called, it created a new file and overwrite it, if you want to append in the file you have to open it by this way:
ofstream file("records_file.txt", ios::app);
See: http://www.cplusplus.com/doc/tutorial/files/
I guess you are using something like while(!bla.eof()), if so then it reaches the end of the buffer but it needs to go past it to raise the flag, so you have the same output twice at the end.

Why does my program infinitely loop?

The program that I'm working on reads an input file's contents (.csv), creates an output file (.txt), and outputs the input file's content in the output file in a formatted fashion. Here's how it looks:
#include "stdafx.h"
#include <iostream> // standard input/output library
#include <string> // string data type and its associated functions
#include <fstream> // file input/output
using namespace std; // use standard namespaces
const int iRows = 1119; // input file contains 1,119 rows
const int iColumns = 11; // input file contains 11 columns
string strData[iRows][iColumns]; // 2-dimensional array that holds input file contents
// pads strings to make them the same wide, for fixed width output
string Align(string strIn, int iWidth)
{
string strOut; // padding
// add padding
for (int i = 0; i < iWidth - strIn.length(); i++)
strOut += " ";
return strOut; // return padding
}
// main program entry point
int main()
{
ifstream inFile; // handle for input file
string strSourcePath = // input file path
"C:\\Users\\Logan\\Documents\\CIS022_S2017_Lab8b.csv";
ofstream outFile; // handle for output file
string strDestPath = // output file path
"C:\\Users\\Logan\\Documents\\out.txt";
inFile.open(strSourcePath); // open input file for read (ifstream)
for (int i = 0; i < iRows; i++) // loop for rows
for (int j = 0; j < iColumns; j++) // embedded loop for column
{
if (j == iColumns - 1) // the last element in the row is newline delimited
getline(inFile, strData[i][j], '\n');
else // all other elements are comma delimited
getline(inFile, strData[i][j], ',');
/*cout << "i = " << i << " j = " << j << " " << strData[i][j] << endl;*/ // console dump for error checking
}
inFile.close(); // done with input file, close it
outFile.open(strDestPath); // open output file for write (ofstream)
for (int i = 0; i < iRows; i++) // loop through each input row
{
outFile <<
strData[i][0] << Align(strData[i][0], 7) << // CRN
strData[i][1] << Align(strData[i][1], 6) << // Subject
strData[i][2] << Align(strData[i][2], 6) << // Number
strData[i][3] << Align(strData[i][3], 20) << // Title
strData[i][4] << Align(strData[i][4], 7) << // Days
strData[i][5] << Align(strData[i][5], 13) << // Meetdates
strData[i][6] << Align(strData[i][6], 17) << // Times
strData[i][7] << Align(strData[i][7], 6) << // Credits
strData[i][8] << Align(strData[i][8], 13) << // Instructor
strData[i][9] << Align(strData[i][9], 6) << // Room
strData[i][10] << endl; // Max Enroll
}
outFile.close(); // close output file
system("Pause"); // wait for user input
return 0; // exit program
}
However, whenever I run it, it loops infinitely here:
for (int i = 0; i < iRows; i++) // loop through each input row
{
outFile <<
strData[i][0] << Align(strData[i][0], 7) << // CRN
strData[i][1] << Align(strData[i][1], 6) << // Subject
strData[i][2] << Align(strData[i][2], 6) << // Number
strData[i][3] << Align(strData[i][3], 20) << // Title
strData[i][4] << Align(strData[i][4], 7) << // Days
strData[i][5] << Align(strData[i][5], 13) << // Meetdates
strData[i][6] << Align(strData[i][6], 17) << // Times
strData[i][7] << Align(strData[i][7], 6) << // Credits
strData[i][8] << Align(strData[i][8], 13) << // Instructor
strData[i][9] << Align(strData[i][9], 6) << // Room
strData[i][10] << endl; // Max Enroll
}
The input file contains 1119 rows of information, so I'll give you the first row:
CRN,Subj,Num,Title,Days,Meetdates,Times,Credits,Instructor,Room,Max Enroll
I let my program sit for a minute and nothing happened. Even adding this code at the beginning of the for loop only outputs the first row of information:
cout <<
strData[i][0] << " " <<
strData[i][1] << " " <<
strData[i][2] << " " <<
strData[i][3] << " " <<
strData[i][4] << " " <<
strData[i][5] << " " <<
strData[i][6] << " " <<
strData[i][7] << " " <<
strData[i][8] << " " <<
strData[i][9] << " " <<
strData[i][10] << endl;
Why does my program infinitely loop?
What happens if, in this code,
string Align(string strIn, int iWidth)
{
string strOut; // padding
// add padding
for (int i = 0; i < iWidth - strIn.length(); i++)
strOut += " ";
return strOut; // return padding
}
strIn is longer than iWidth ?
You will attempt to increment i until it reaches a negative number.
Here is probably your issue.

fstream << operate not writing entire output

Everthing goes well until the f << "string" << temp_int << endl; statement
get different results with different openmodes, either doesn't write at all or writes the first two chars of "NumberSaves"
unsigned int temp_int = 0;
fstream f("resources/saveData/Player/savelog.txt");
if (!f)
{
cout << "error accessing savelist" << endl;
}
else
{
string skip;
std::stringstream iss;
string line;
readVarFromFile(f, iss, skip, line, { &temp_int }); //check how many saves currently
temp_int += 1; //increment number of saves by 1
f.seekp(ios_base::beg);
cout << "Write position: " << f.tellp() << endl; //check stream is at beginning
f << "<NumberSaves>" << temp_int << endl; //truncate <NumberSaves> 'x' with <NumberSaves> 'x + 1'
cout << "Write position: " << f.tellp() << endl; //position suggests the entire string has been written, only two characters have been
if (!f)
{
cout << "ERROR";
}
f.seekp(ios_base::end);
f << currentPlayer->getName(); //append players name to end of file
}
desired output is as follows
NumberSaves 2
player
anotherplayer
current output
Nu
player
Use seekp properly like this:
os.seekp(0, std::ios_base::end); // means bring me to 0 from the end of file.
look at the example code in
http://en.cppreference.com/w/cpp/io/basic_ostream/seekp
std::ios_base::end is a direction not an absolute position. It is just an enum value. The value is probably 2 and that is why it brings you to position 2 inside the file.

[C++]Importing text file - Problems with getline()

I've been having trouble with reading text files in c++, particularly when assigning a line to a variable.
I have the following code:
ifstream fx;
fx.open(nomeFich);
if(!fx)
{cout << "FX. nao existe!" <<endl;}
string linha="";;
int pos, inic;
while(!fx.eof())
{
getline(fx,linha);
if(linha.size() > 0)
{
cout << linha << endl;
inic=0;
pos=0;
pos=linha.find(",",inic);
cout << pos << endl;
string nomeL1(linha.substr(inic,pos));
cout << "atribuiu 1" << endl;
inic=pos;
cout <<"inic: " << inic << " pos:" << pos <<endl;
pos=linha.find(',',inic);
string nomeL2(linha.substr(inic,pos));
cout << "atribuiu 2" << endl;
inic=pos;
cout <<"inic: " << inic << " pos:" << pos <<endl;
pos=linha.find(',',inic);
cout << "atribuiu 3" << endl;
string dist(linha.substr(inic,pos));
When it does the cout << linha << endl; it returns something like :
= = == = = = = == = = = = = = = == = = = = == = = = = =
I've googled it quite a lot and can't find an answer.
I'm new to C++ so don't bash too much xD
Don't do this:
while(!fx.eof())
{
getline(fx,linha); // Here you have to check if getline() actually succeded
// before you do any further processing.
// You could add if (!fx) { break;}
// STUFF;
}
But the better design is:
while(getline(fx,linha)) // If the read works then enter the loop otherwise don't
{
// STUFF
}
You are failing to move past the comma:
inic=pos; // pos is the position of the last ',' or std::string::npos
pos=linha.find(',',inic); // So here pos will be the same as last time.
// As you start searching from a position that has a comma.
ifstream has a getline function, it accepts a char* as the first parameter and the maximum length as the second.
ifstream also has operator>> which you should be using for input, but it will read till whitespace, which is not what you want.
::getline that you're using should also work, but that's assuming that the stream is OK, which as mentioned before, you don't check correctly. You should be checking for errors after calling it, because if you reach EOF or there's an error, you won't know until the whole loop is done.
Also, what's in the file? Maybe what you're getting is the correct result?