Why doesn't this change the .txt file? - c++

I'm trying to edit a text file to remove the vowels from it and for some reason nothing happens to the text file. I think it may be because a mode argument needs to be passed in the filestream.
[SOLVED]
Code:
#include "std_lib_facilities.h"
bool isvowel(char s)
{
return (s == 'a' || s == 'e' || s =='i' || s == 'o' || s == 'u';)
}
void vowel_removal(string& s)
{
for(int i = 0; i < s.length(); ++i)
if(isvowel(s[i]))
s[i] = ' ';
}
int main()
{
vector<string>wordhold;
cout << "Enter file name.\n";
string filename;
cin >> filename;
ifstream f(filename.c_str());
string word;
while(f>>word) wordhold.push_back(word);
f.close();
ofstream out(filename.c_str(), ios::out);
for(int i = 0; i < wordhold.size(); ++i){
vowel_removal(wordhold[i]);
out << wordhold[i] << " ";}
keep_window_open();
}

Reading and writing on the same stream results in an error. Check f.bad() and f.eof() after the loop terminates. I'm afraid that you have two choices:
Read and write to different files
Read the entire file into memory, close it, and overwrite the original
As Anders stated, you probably don't want to use operator<< for this since it will break everything up by whitespace. You probably want std::getline() to slurp in the lines. Pull them into a std::vector<std::string>, close the file, edit the vector, and overwrite the file.
Edit:
Anders was right on the money with his description. Think of a file as a byte stream. If you want to transform the file in place, try something like the following:
void
remove_vowel(char& ch) {
if (ch=='a' || ch=='e' || ch=='i' || ch =='o' || ch=='u') {
ch = ' ';
}
}
int
main() {
char const delim = '\n';
std::fstream::streampos start_of_line;
std::string buf;
std::fstream fs("file.txt");
start_of_line = fs.tellg();
while (std::getline(fs, buf, delim)) {
std::for_each(buf.begin(), buf.end(), &remove_vowel);
fs.seekg(start_of_line); // go back to the start and...
fs << buf << delim; // overwrite the line, then ...
start_of_line = fs.tellg(); // grab the next line start
}
return 0;
}
There are some small problems with this code like it won't work for MS-DOS style text files but you can probably figure out how to account for that if you have to.

Files are sort of like a list, a sequential byte stream. When you open the file you position the file pointer at the very start, every read/write repositions the file pointer in the file with an offset larger than the last. You can use seekg() to move back in the file and overwrite previous content. Another problem with your approach above is that there will probably be some delimiters between the words typically one or more spaces for instance, you will need to handle read/write on these too.
It is much easier to just load the whole file in memory and do your manipulation on that string then rewriting the whole thing back.

Are you sure your while loop is actually executing? Try adding some debugging output to verify that it's doing what you think it is.

Related

tellg returning negative value after using getline and seekg

I am needing to parse through a text file and output the vowels within this textfile line by line into a separate file. This all needs to be done using one filestream at a time.
I open the input stream using fstream ios:: in & ios::binary, read in one line using getline, clear & close that stream then open my output fstream using ios::out & ios::app, find the vowels and output them one by one as they are found. Then close output stream and open the input stream. Done using seekg to put the file at the end of the prior line in hopes that getline will read in the consecutive line.
fstream a(fileName, ios::in | ios::binary | ios::ate);
string OF = "vowels_" + fileName;
while (count < size && count != -1) {
count = a.tellg();
a.close();
fstream b(OF, ios::out| ios::app);
for (int i = 0; i < line.length(); i++) {
if (line.at(i) == '\n'){b << '\n';}
if (line.at(i) == 'a' || line.at(i) == 'e' || line.at(i) == 'i' || line.at(i) == 'o' || line.at(i) == 'u') {
b << line.at(i); vowels+= line.at(i);
}
}
if(vowels.empty()){b << endl;}
if (line.empty()){b << endl;}
b.close();
fstream a(fileName, ios::in | ios::binary | ios::ate);
}
fstream a(fileName, ios::in | ios::binary | ios::ate); at the end of the loop does not re-initialize a, it creates a new fstream.
When you get to the start of the next iteration, a is closed.
You are overcomplicating this.
Work line by line in text mode, keep track of how many lines you've read, and skip over that many lines on each iteration.
Something like this:
size_t skip_lines = 0;
while (true)
{
std::string line;
{
ifstream in(fileName);
size_t lines = 0;
while (lines < skip_lines && std::getline(in, line))
{
lines++;
}
if (!std::getline(in, line))
{
break;
}
}
skip_lines += 1;
ofstream out(OF, ios::append);
for (auto it: line)
{
if (is_vowel(it)) // Define 'is_vowel' elsewhere.
{
out << it;
}
}
out << '\n';
}
Normally, you would use a different approach. You would simply open both input and output files and then do the task in one shot.
But, obviously, the task is, to have only one stream open at the same time. And maybe, it should teach you how to use tellg and seekg functions.
And one of the most important things that you need to do: Please always check the return value of each function call. For read functions, this is an absolute must.
Then, handle potential errors, or states.
You can and should also use those return values for the control logic in your program.
What you should additionally know is, that, if stream variables go out of scope, they will automatically close the associated file. Please make use of this property.
Then, the solution: We need to set up a variable with the current file position. At the beginning, this is 0. Then we read data and, store the new file position after reading. We can close now the source file, because, we know the position of the next characters to read. So, close the file, and do the “vowel”-work. Simply check, if a character is a vowel, and then write it to the destination file.
Do this, until we get any kind of error of “EOF” (end of file).
A potential solution is shown below. I put many comments in the file for easier understanding.
Please see:
#include <iostream>
#include <fstream>
#include <string>
const std::string sourceFileName{ "test.txt" };
int main() {
// Define the filename for the destination file
std::string destinationFileName = "vowel_" + sourceFileName;
// Here we will store the current file position
std::ifstream::pos_type readPosition{};
// We will use a boolean variable to indicate, how long we want to read
bool continueToRead{ true };
// Read the source file in a while loop
while (continueToRead) {
// Open the source file for reading
std::ifstream sourceFileStream{ sourceFileName };
// Check, if the file could be opened.
// This calls the "bool" function of the stream, to check, if everything is OK
if (sourceFileStream) {
// Source file could be opened. Set the file read pointer to the last known position
if (not sourceFileStream.seekg(readPosition)) {
// If thís did not work (end of file or something)
continueToRead = false;
}
else {
// Read one line and check, if that worked.
std::string line{};
if (std::getline(sourceFileStream, line)) {
// Now we need to store the current file position for the next loop run
readPosition = sourceFileStream.tellg();
// Because, we want to have only one file stream open at a time,
// we will now close the input file stream
sourceFileStream.close();
// We may need to write to the output file. Open this now and check,
// if it could be opened. Set file pointer to end with "app"
std::ofstream destinationFileStream(destinationFileName, std::ios::app);
// Now iterate over the source line string and write vowels, if necessary
for (const char c : line) {
// check, if c is a vowel
if ((c == 'a') or (c == 'e') or (c == 'i') or (c == 'o') or (c == 'u')
or (c == 'A') or (c == 'E') or (c == 'I') or (c == 'O') or (c == 'U')) {
// Write vowel to destination file
destinationFileStream << c;
}
}
// Write final newline character
destinationFileStream << std::endl;
// Here the destinationFileStream will go out of scope and closed automatically
}
else {
// Could not read data. Stop loop
continueToRead = false;
}
}
}
else {
// Source file could not be opened.
std::cout << "\nError: Could not open source file '" << sourceFileName << "'\n\n";
continueToRead = false;
}
}
}

How to replace Hi with Bye in a file

I want to replace hi with a bye by reading a file and outputting another file with the replaced letters.
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream myfile;
ofstream output;
output.open("outputfile.txt");
myfile.open("infile.txt");
char letter;
myfile.get(letter);
while (!myfile.eof()) {
if (letter == 'H') {
char z = letter++;
if (z == 'i')
output << "BYE";
}
else output << letter;
}
output.close();
myfile.close();
return 0;
}
My outputs are repeated capital I's that is repeated infinity times.
Here is my input file
Hi
a Hi Hi a
Hi a a Hi
Don't check eof
The eof method is returning the location of the input stream read pointer, and not the status of the get. It is more like telling you whether or not get will succeed, so you could write something like:
while (!myfile.eof()) {
char letter;
myfile.get(letter);
//...
}
In this way, you would at least be getting a new letter at each iteration, and the loop ends when the read pointer reaches the end of the input.
But, there are other cases that might cause the get to not succeed. Fortunately, these are captured by the stream itself, which is returned by get. Testing the status of the stream is as easy as treating the stream as a boolean. So, a more idiomatic way to write the loop is:
char letter;
while (myfile.get(letter)) {
//...
}
Peek at the next letter
When you want to look at the next letter in the input following the detected 'H', you perform an increment.
char z = letter++;
But, this does not achieve the desired result. Instead, it just sets both letter and z variables to the numerical successor of 'H' ('H' + 1), and does not observe the next letter in the input stream.
There is another method you can use that is like get, but leaves the input in the input stream. It is called peek.
char z;
auto peek = [&]() -> decltype(myfile) {
if (myfile) z = myfile.peek();
return myfile;
};
if (peek()) {
//...
}
And now, you can check the value of z, but it is still considered input for the next get on letter.
Close to what you implemented
So, the complete loop could look like:
char letter;
while (myfile.get(letter)) {
if (letter == 'H') {
char z;
auto peek = [&]() -> decltype(myfile) {
if (myfile) z = myfile.peek();
return myfile;
};
if (peek() && z == 'i') {
myfile.get(z);
output << "BYE";
continue;
}
}
output << letter;
}
With this approach, you will be able to correctly handle troublesome cases like HHi as input, or the last letter in the input being an H.
Your two lines:
myfile.get(letter);
while (!myfile.eof()) {
are wrong.
First off you only read letter once, hence your infinite loop.
Secondly you don't use eof in a while loop.
You want something more like:
while (myfile.get(letter)) {
Also:
char z = letter++;
is wrong, you want to read another letter:
myfile.get(z);
but you have to be careful that you get something, so
if(!myfile.get(z)) {
output << letter;
break;
}
So finally:
char letter;
while (myfile.get(letter)) {
if (letter == 'H') {
char z;
if(!myfile.get(z)) {
output << letter;
break;
}
if (z == 'i') {
output << "BYE";
}
else output << letter << z;
}
else output << letter;
}
But now we are consuming the character after any H which may not be desirable.
See #jxh's answer for a way to do this with look ahead.
There is a dedicated function to replace patterns in strings. For example, you could use std::regex_replace. That is very simple. We define, what should be searched for and with what that would be replaced.
Some comments. On StackOverflow, I cannot use files. So in my example program, I use a std::istringstream instead. But this is also an std::istream. You can use any other std::istream as well. So if you define an std::ifstream to read from a file, then it will work in the same way as the std::istringstream. You can simply replace it. For the output I use the same mechanism to show the result on the console.
Please see the simple solution:
#include <iostream>
#include <sstream>
#include <regex>
// The source file
std::istringstream myfile{ R"(Hi
a Hi Hi a
Hi a a Hi)" };
// The destination file
std::ostream& output{ std::cout };
int main() {
// Temporary string, to hold one line that was read from a file
std::string line{};
// Read all lines from the file
while (std::getline(myfile, line)) {
// Replace the sub-string and write to output file
output << std::regex_replace(line, std::regex("Hi"), "Bye") << "\n";
}
return 0;
}

ifstream and ofstream: How do I perform multiple modifications to a file?

I've been googling this question for a few hours and can't seem to find anything that addresses it.
I'm reeaaaally hazy on file operations in C++, but I've spent about 20 of the last 36 hours reading documentation and forum questions trying to get a project for a friend together.
Say I've got a file called raw_questions.txt, and I'd like to make some changes to it. This file is a study guide for an exam, and has a question followed by 4 multiple-choice answers. I want to remove blank lines and add some tokens to allow another program I'm working on to parse it. I've written a formatter program to perform those operations. The operations are:
Remove blank lines from source file as it appears it's double-spaced
Add a delimiter character ('#') to the end of each question and
answer.
Using the delimiter, read each question and answer in as a string
and append it to an output file with a token at the beginning of
question or answer, which will let my other program know whether a
line contains a question or answer.
My question: I'm stuck at how to move from one operation to the next. My current approach is to read each line into a string, perform the operation on the string, and to add the new string to an output file. Using this approach, to perform the next operation I have to open the previous operation's output file as my new input file, and make a new output file for that operation. I feel like there's got to be a better way, but like I said, I'm pretty hazy on file operations in C++. What should I be doing in this situation?
I've considered creating an ifstream and ofstream that both point to the same file, and hoping that when the ifstream file is opened, it will store a temporary copy in memory. Then, after I read line by line and write to my ofstream object, when it closes it will overwrite my old file. I don't know if that makes any sense, and I don't think that's even how fstream works.
The code that I have so far:
#include <fstream>
#include <string>
#include "Debug.h"
Debug debugger;
void remove_empty_lines (std::ifstream& input, std::ofstream& output);
void insert_delimiter (std::ifstream& input, std::ofstream& output, char delimiter);
void create_output (std::ifstream& input, std::ofstream& output);
int main() {
debugger.set_active();
char delimiter = '#';
std::ifstream input;
std::ofstream output;
input.open("questions_source.txt");
output.open("questions_intermidiate.txt");
remove_empty_lines (input, output);
}
void remove_empty_lines (std::ifstream& input, std::ofstream& output) {
while (!input.eof()) {
std::string line;
std::getline(input, line);
if (line != "") {
output << line << std::endl;
}
}
}
void insert_delimiter(std::ifstream& input, std::ofstream& output) {
}
// This function doesn't quite work, WIP - Please ignore
void create_output(std::ifstream& input, std::ofstream& output) {
std::string line;
for (int i = 1; !input.eof(); i++) {
debugger.out("Inserting tokens.");
bool found = false;
while (!found) {
getline (input, line);
if (i < 10) {
if (line[1] == ')') {
line.erase (0, 3);
output << "[" << i << "]" << line << std::endl;
debugger.out("Found line: " + line);
found = true;
}
} else if (i < 100) {
if (line[2] == ')') {
line.erase (0, 4);
output << "[" << i << "]" << line << std::endl;
debugger.out("Found line: " + line);
found = true;
}
}
}
for (int j = 0; j < 4; j++) {
getline (input, line);
if (line[1] == ')') {
line.erase (0, 3);
output << "[" << i << "a]" << line << std::endl;
}
}
}
}
I'm also trying to teach myself git at the moment, so I happen to have the project I'm working on hosted on github here. I don't know if the context will make what I'm trying to do make sense, but I'm posting it just in case.
Bonus question: I've been racking my brain, but I haven't come up with a solution to adding the delimiter. Answers seem to be one line long, so I can probably just add the delimiter to the end of any line starting with "A)" etc., but some of the questions are much longer. My thought is to find any occurrence of "A)" and add the delimiter to the end of the line above it, but I can't think of how to do that. Can anyone point me in the right directions for member functions of fstream that might help?
Thanks for reading.
Streams do not magically read the entire file into memory. If that is what you want to do, you should just do that: my guess is that your file is considerably smaller than your available memory, and it might be easier to perform all the operations in place using standard C++ containers.

How can I label the lines of an existing file?

Lets say I have a text file containing something like:
Four
score
and
seven
years
ago
...
I want to be able to label these lines so that after the program runs, the file looks like:
1.Four
2.score
3.and
4.seven
5.years
6.ago
...
I've prepared a solution; however, I find it to be heavy weight and it has a problem of labeling one past the last line...
std::string file = "set_test - Copy.txt";
std::ifstream in_test{file};
std::vector<std::string> lines;
while(in_test) {
std::string temp;
getline(in_test, temp);
lines.push_back(temp);
}
in_test.close();
std::ofstream out_test{file};
for(unsigned int i = 0; i < lines.size(); ++i) {
out_test << i+1 << '.' << lines[i] << '\n';
}
On top of being heavy-weight, this solution also labels the line beyond the last line of text.
Does anyone have a better solution to this problem?
The cause of your problem is this structure
while (stream is good)
read from stream
do something
as it will read too much. (See this Q&A for explanation.)
What's happening is that the very last getline, the one that actually reaches the end of the file, will fail and leave temp empty.
Then you add that empty line to your lines.
The "canonical" stream-reading loop structure is
while (attempt to read)
do something with the result
in your case,
std::string temp;
while (getline(in_test, temp)) {
lines.push_back(temp);
}
If you write to a different file you don't need to store anything except the last line; you can write each line immediately.
If you want to replace the original, you can replace the old with the new afterwards.
Something like this:
std::ifstream in_test{"set_test - Copy.txt";}
std::ofstream out_test{"set_test - Numbered.txt"};
if (!in_test || !out_test) {
std::cerr << "There was an error in the opening of the files.\n";
return;
}
int i = 1;
std::string line;
while (getline(in_test, line) && out_test << i << '.' << line << '\n') {
i++;
}

How to Read from a Text File, Character by Character in C++

I was wondering if someone could help me figure out how to read from a text file in C++, character by character. That way, I could have a while loop (while there's still text left) where I store the next character in the text document in a temp variable so I could do something with it, then repeat the process with the next character. I know how to open the file and everything, but temp = textFile.getchar() doesn't seem to work.
You could try something like:
char ch;
fstream fin("file", fstream::in);
while (fin >> noskipws >> ch) {
cout << ch; // Or whatever
}
#cnicutar and #Pete Becker have already pointed out the possibility of using noskipws/unsetting skipws to read a character at a time without skipping over white space characters in the input.
Another possibility would be to use an istreambuf_iterator to read the data. Along with this, I'd generally use a standard algorithm like std::transform to do the reading and processing.
Just for example, let's assume we wanted to do a Caesar-like cipher, copying from standard input to standard output, but adding 3 to every upper-case character, so A would become D, B could become E, etc. (and at the end, it would wrap around so XYZ converted to ABC.
If we were going to do that in C, we'd typically use a loop something like this:
int ch;
while (EOF != (ch = getchar())) {
if (isupper(ch))
ch = ((ch - 'A') +3) % 26 + 'A';
putchar(ch);
}
To do the same thing in C++, I'd probably write the code more like this:
std::transform(std::istreambuf_iterator<char>(std::cin),
std::istreambuf_iterator<char>(),
std::ostreambuf_iterator<char>(std::cout),
[](int ch) { return isupper(ch) ? ((ch - 'A') + 3) % 26 + 'A' : ch;});
Doing the job this way, you receive the consecutive characters as the values of the parameter passed to (in this case) the lambda function (though you could use an explicit functor instead of a lambda if you preferred).
To quote Bjarne Stroustrup:"The >> operator is intended for formatted input; that is, reading objects of an expected type and format. Where this is not desirable and we want to read charactes as characters and then examine them, we use the get() functions."
char c;
while (input.get(c))
{
// do something with c
}
Here is a c++ stylish function your can use to read files char by char.
void readCharFile(string &filePath) {
ifstream in(filePath);
char c;
if(in.is_open()) {
while(in.good()) {
in.get(c);
// Play with the data
}
}
if(!in.eof() && in.fail())
cout << "error reading " << filePath << endl;
in.close();
}
//Variables
char END_OF_FILE = '#';
char singleCharacter;
//Get a character from the input file
inFile.get(singleCharacter);
//Read the file until it reaches #
//When read pointer reads the # it will exit loop
//This requires that you have a # sign as last character in your text file
while (singleCharacter != END_OF_FILE)
{
cout << singleCharacter;
inFile.get(singleCharacter);
}
//If you need to store each character, declare a variable and store it
//in the while loop.
Re: textFile.getch(), did you make that up, or do you have a reference that says it should work? If it's the latter, get rid of it. If it's the former, don't do that. Get a good reference.
char ch;
textFile.unsetf(ios_base::skipws);
textFile >> ch;
Assuming that temp is a char and textFile is a std::fstream derivative...
The syntax you're looking for is
textFile.get( temp );
There is no reason not to use C <stdio.h> in C++, and in fact it is often the optimal choice.
#include <stdio.h>
int
main() // (void) not necessary in C++
{
int c;
while ((c = getchar()) != EOF) {
// do something with 'c' here
}
return 0; // technically not necessary in C++ but still good style
}