Edit file line by line number - C++ - c++

I'm trying to edit a .dat file. I want to read a line by line number, turn the content to int, edit and replace it.
like I want to edit line number 23, it says "45" I need to make it "46". How do I do that?
ofstream f2;
theBook b;
f2.open("/Users/vahidgr/Documents/Files/UUT/ComputerProjects/LibraryCpp/LibraryFiles/Books.dat", ios::app);
ifstream file("/Users/vahidgr/Documents/Files/UUT/ComputerProjects/LibraryCpp/LibraryFiles/Books.dat");
cout<<"In this section you can add books."<<endl;
cout<<"Enter ID: "; cin>>b.id;
cout<<"Enter Name: "; cin>>b.name;
string sID = to_string(b.id);
string bookName = b.name;
string line;
int lineNumber = 0;
while(getline(file, line)) {
++lineNumber ;
if(line.find(bookName) != string::npos && line.find(sID) != string::npos) {
int countLineNumber = lineNumber + 4;
registered = true;
f2.close();
break;
}
}
Inside the file:
10000, book {
author
1990
20
20
}

If your file is small (such as under 1GB), you can just read the entire file into memory line-by-line as a std::vector<std::string> (Hint: use std::getline). Then, edit the required line, and overwrite the file with an updated one.

Iterate Byte for Byte through the file and count line breaks (\n or \r\n on Windows).
After 22 breaks, insert bytes that say “46”. It should overwrite the existing bytes.

If your modifications are the exact size of the original text, you can write back to the same file. Otherwise, you will need to write your modifications to a new file.
Since your file is variable length text, separated by newlines, we'll have to skip lines until we get to the desired line:
const unsigned int desired_line = 23;
std::ifstream original_file(/*...*/);
std::ofstream modified_file(/*...*/);
// Skip lines
std::string text_line;
for (unsigned int i = 0; i < desired_line - 1; ++i)
{
std::getline(original_file, text_line);
modified_file << text_line << std::endl;
}
// Next, read the text, modify and write to the original file
//... (left as an exercise for the OP, since this was not explicit in the post.
// Write remaining text lines to modified file
while (std::getline(original_file, text_line))
{
modified_file << text_line << std::endl;
}
Remember to write your modified text to the modified file before copying the remaining text.
Edit 1: By record / object
This looks like an X-Y problem.
A preferred method is to read in the objects, modify the object, then write the objects to a new file.

Related

Picking a random line from a text file

I need to write an 8 ball code that has eleven options to display and it needs to pull from a text file. I have it taking lines from the text file but sometimes it takes an empty line with no writing. And I need it to only take a line that has writing.
Here are that options it needs to draw from:
Yes, of course!
Without a doubt, yes.
You can count on it.
For sure!Ask me later.
I'm not sure.
I can't tell you right now.
I'll tell you after my nap.
No way!I don't think so.
Without a doubt, no.
The answer is clearly NO.
string line;
int random = 0;
int numOfLines = 0;
ifstream File("file.txt");
srand(time(0));
random = rand() % 50;
while (getline(File, line))
{
++numOfLines;
if (numOfLines == random)
{
cout << line;
}
}
}
IMHO, you need to either make the text lines all the same length, or use a database (table) of file positions.
Using File Positions
Minimally, create a std::vector<pos_type>.
Next read the lines from the file, recording the file position of the beginning of the string:
std::vector<std::pos_type> text_line_positions;
std::string text;
std::pos_type file_position = 0;
while (std::getline(text_file, text)
{
text_line_positions.push_back(file_position);
// Read the start position of the next line.
file_position = text_file.tellg();
}
To read a line from a file, get the file position from the database, then seek to it.
std::string text_line;
std::pos_type file_position = text_line_positions[5];
text_file.seekg(file_position);
std::getline(text_file, text_line);
The expression, text_line_positions.size() will return the number of text lines in the file.
If File Fits In Memory
If the file fits in memory, you could use std::vector<string>:
std::string text_line;
std::vector<string> database;
while (getline(text_file, text_line))
{
database.push_back(text_line);
}
To print the 10 line from the file:
std::cout << "Line 10 from file: " << database[9] << std::endl;
The above techniques minimize the amount of reading from the file.

How to delete/update specific data in txt file using C++?

I'm writing a library system using struct as seen
struct Book
{
char name[50];
int ID;
char author[50];
double price;
int copies;
};
and the file is organised as seen.
ID Name Author Price Copies
1 HarryPotter Lol 30 5
2 EnglishMan English 30 5
3 Spiderman Marvel 30 5
4 Avengers Marvel 30 5
Let's say I want to use the program to update book no. 2 (EnglishMan) and change its name to IronMan, how can I do that using files?
If you use plain text files as data storage you just have to follow this inconvenient workflow:
Read the complete file into your data structures.
Alter the data.
Truncate or remove the file.
Write all the data into the file.
There are ugly hacks to edit parts of the file, but they don't make things better.
For managing tabular data, as in your example, relational databases have been invented a long time ago. Start to learn SQLite, and your life will be much easier in the long run.
What you are doing is, essentially, trying to create your own database which is counterproductive at best. But if it's for learning file I/O and string streams, following code can help you understand the concepts although It's probably not the most efficient way of doing things.
In general, as #Murphy said, you need to read a file, copy it to a buffer, adjust the buffer to your liking, truncate the file and write your own buffer to the file.
int searchbyID(string filename, string ID, string newName);
int main()
{
searchbyID("d:\\test.txt", "2", "silmarillion");
}
int searchbyID(string filename, string ID, string newName)
{
// open an input file stream
ifstream inputfile(filename);
if (!inputfile)
return -1; // couldn't open the file
string line,word;
string buffer;
// read the file line by line
while (getline(inputfile, line))
{
std::stringstream record(line);
//read the id from the file and check if it's the asked one
record >> word;
if (word == ID)
{
// append the ID first
buffer += ID + "\t";
//append the new name instead of the old one
buffer += newName + "\t";
//pass the old name
record >> word;
//copy the rest of the line just as it is
while (record >> word)
buffer += "\t" + word + "\t";
buffer += "\n";
}
else
{
//if not, just pass the line as it is
buffer += line + "\n";
}
}
// close input file stream
inputfile.close();
// open an output file stream
ofstream outputfile(filename);
// write new buffer to the file
outputfile << buffer;
//close the output file stream
outputfile.close();
return 0;
}

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!)

Error copying and pasting data from a file to another

I am writing a code to merge multiple text files and output a single file.
There can be up to 22 input text files which contain 1400 lines each.
Each line has 8 bits of binary and the new line characters \n.
I am out putting a single file that has all 22 text files merged.
Problem is with my output file, after 1400 lines it appears that the content from the previous file is still being placed into output file(although the length of the previous file was 1400 lines). This extra content also begins to have additional line space between each row if opened by microsoft office or sublime, however it is interpreted as a single line if opened by notepad or excel(a single cell in excel).
Following is the picture of expected behaviour of the output file,
Here is a picture of abnormal behaviour. This starts when the first file finishes.
I know this data is from the first file still because the second file starts from 00000000
And here is the start of the second file,
And this abnormal behavior repeats every single time the files are switching.
My implementation to achieve this is as follows:
repeat:
if(user_input == 'y')
{
fstream data_out ("data.txt",fstream::out);
for(int i = 0; i<files_found; i++)
{
fstream data_in ((file_names[i].c_str()),fstream::in);
if(data_in.is_open())
{
data_in.seekg(0,data_in.end);
long size = data_in.tellg();
data_in.seekg(0,data_in.beg);
char * buffer = new char[size];
cout << size;
data_in.read(buffer,size);
data_out.write(buffer,size);
delete[] buffer;
}else
{
cout << "Unexpected error";
return 1;
}
data_in.close();
}
data_out.close();
}else if(user_input == 'n')
{
return 1;
}else
{
cout << "Input not recognised. Type y for Yes, and n for No";
cin >> user_input;
goto repeat;
}
Further information:
I have checked the size variable and it is as I expect, 14000.
8 bits, and a \ with n = 10 characters per line,
1400 rows x 10 = 14000.
Assuming reader of code to be experienced.
Sorry to bump this question, but I really like question that are marked as answered. JoachimPileborg answer seems to have worked for you:
Also, instead of seeking and checking sizes and allocating memory, why
not just do e.g. data_out << data_in.rdbuf();? This will copy the
whole input file to the output. – Joachim Pileborg Jul 29 at 17:26
A reference http://www.cplusplus.com/reference/ios/ios/rdbuf/ and an example:
#include <fstream>
#include <string>
#include <vector>
int main(int argc, char** argv)
{
typedef std::vector<std::string> Filenames;
Filenames vecFilenames;
// Populate the list of file names
vecFilenames.push_back("Text1.txt");
vecFilenames.push_back("Text2.txt");
vecFilenames.push_back("Text3.txt");
// Merge the files into Output.txt
std::ofstream fpOutput("Output.txt");
for (Filenames::iterator it = vecFilenames.begin();
it != vecFilenames.end(); ++it)
{
std::ifstream fpInput(it->c_str());
fpOutput << fpInput.rdbuf();
fpInput.close();
}
fpOutput.close();
return 0;
}

how to replace a line with another/ c++ code

I am working on ubuntu. I have a file called test.txt. I would like to replace the second line with another line. How can I do that? I don't want to create a new file and delete the first one.
I would like to specify that the lenght of the new line is the same with the length of the ond one
Try something like:
#include <fstream>
#include <string>
int main() {
const int lineToReplace = 14;
std::fstream file("myfile.txt", std::ios::in | std::ios::out);
char line[255];
for (int i=0; i<lineToReplace-1; ++i)
file.getline(line, 255); // This already skips the newline
file.tellg();
file << "Your contents here" << std::endl;
file.close();
return 0;
}
Note that line can hold up to 254 bytes (plus the null terminator), so if your line takes more than that, adjust accordingly.
If the file is small enough you can read it into memory, do whatever modifications you want on the in-memory copy, and the write if back out.
Edit Code as requested:
// A vector to store all lines
std::vector<std::string> lines;
// The input file
std::ifstream is("test.txt")
// Get all lines into the vector
std::string line;
while (std::getline(is, line))
lines.push_back(line);
// Close the input file
is.close();
// All of the file is now in memory, each line a single entry in the vector
// "lines". The items in the vector can now be modified as you please.
// Replace the second line with something else
lines[1] = "Something else";
// Open output file
std::ofstream os("test.txt");
// Write all lines to the file
for(const auto& l : lines)
os << l << '\n';
// All done, close output file
os.close();
This is Python, but it's significantly more readable and terse for this purpose:
f = open('text.txt', 'w+') # open for read/write
g = tempfile.TemporaryFile('w+') # temp file to build replacement data
g.write(next(f)) # add the first line
next(f) # discard the second line
g.write(second_line) # add this one instead
g.writelines(f) # copy the rest of the file
f.seek(0) # go back to the start
g.seek(0) # start of the tempfile
f.writelines(g) # copy the file data over
f.truncate() # drop the rest of the file
You could also use shutil.copyfileobj instead of writelines to do block copying between the files.
Here's how I would do it, without a hard limit on the line length:
#include <fstream>
#include <string>
using namespace std;
int main()
{
fstream file("test.txt",std::ios::in|std::ios::out);
string line;
string line_new="LINE2";
// Skip the first line, file pointer points to beginning of second line now.
getline(file,line);
fstream::pos_type pos=file.tellg();
// Read the second line.
getline(file,line);
if (line.length()==line_new.length()) {
// Go back to start of second line and replace it.
file.seekp(pos);
file << line_new;
}
return 0;
}