Swapping chars in a file, multiple times - c++

I have code that works but only once. I need an input char a to be swapped with an input char b. The first time through the loop, it swaps the two selected chars fine, but on the second and following iterations it does nothing but keep the outFile the same. How can I swap more than two chars until I want to stop?
ifstream inFile("decrypted.txt");
ofstream outFile("swapped.txt");
const char exist = 'n';
char n = '\0';
char a = 0;
char b = 0;
cout<<"\nDo u want to swap letters? press <n> to keep letters or any button to continue:\n"<<endl;
cin>>n;
while (n != exist)
{
cout<<"\nWhat is the letter you want to swap?\n"<<endl;
cin>>a;
cout<<"\nWhat is the letter you want to swap it with?\n"<<endl;
cin>>b;
if (inFile.is_open())
{
while (inFile.good())
{
inFile.get(c);
if( c == b )
{
outFile<< a;
}
else if (c == a)
{
outFile<< b;
}
else
{
outFile<< c;
}
}
}
else
{
cout<<"Please run the decrypt."<<endl;
}
cout<<"\nAnother letter? <n> to stop swapping\n"<<endl;
cin>>n;
}

Consider a different approach.
Collect all the character swaps in a lookup table. By default translate['a'] == 'a', the input character is the same as the output character. To swap a with z just set translate['a'] = 'z' and translate['z'] = 'a'.
Then perform a single pass over the file, copying and translating at the same time.
#include <array>
#include <fstream>
#include <iostream>
#include <numeric>
int main()
{
std::array<char,256> translate;
std::iota(translate.begin(), translate.end(), 0); // identity function
for (;;)
{
char a, b;
std::cout << "\nEnter ~ to end input and translate file\n";
std::cout << "What is the letter you want to swap? ";
std::cin >> a;
if (a == '~') break;
std::cout << "What is the letter you want to swap it with? ";
std::cin >> b;
if (b == '~') break;
std::swap(translate[a], translate[b]); // update translation table
}
std::ifstream infile("decrypted.txt");
std::ofstream outfile("swapped.txt");
if (infile && outfile)
{
std::istreambuf_iterator<char> input(infile), eof;
std::ostreambuf_iterator<char> output(outfile);
// this does the actual file copying and translation
std::transform(input, eof, output, [&](char c){ return translate[c]; });
}
}

You have read the entire file, and as such will not read more bytes or write more bytes. You can use seek to get back to the beginning, or simply close and re-open the files.

Related

std::getline not re-requesting user input in while loop [duplicate]

This question already has answers here:
Why does std::getline() skip input after a formatted extraction?
(5 answers)
Closed 2 years ago.
I am trying to write a program where a user is asked for input and then I check if the 2 sub strings (splitting in half) is the mirror image. I want to loop continually until a user stops entering in input but my code is not looping correctly. The code works fine on the first iteration of the while loop, but then during the second iteration of the while loop, getline() does not re-request user input, it just sets std::string input to "".
I'm sure I am missing something obvious.
Code
#include <iostream>
#include <string>
#include <stack>
#include <algorithm>
using namespace std;
bool compareMirrorString(const std::string&, const std::string&);
int main()
{
std::string input;
string first_string;
string second_string;
bool result;
int input_length;
int first_string_length;
std::string quit;
cout << "Enter 2 strings to compare, seperated by a # e.g. \"abc#cba\"." << endl;
while ( true )
{
std::getline(std::cin, input);
//split string into its two component parts
input_length = input.length();
first_string_length = input_length / 2;
first_string.assign(input, 0, first_string_length);
second_string.assign(input, first_string_length + 1);
//test if two strings are mirror images of each other
result = compareMirrorString(first_string, second_string);
if (result) {
cout << "Yes they match.";
}
else {
cout << "No they do not match.";
}
cout << "\nDo you want to test another string? Y for yes, q to quit." << endl;
cin >> quit;
if (quit == "q" or quit == "Q" or quit == "quit" or quit == "Quit" or quit == "QUIT"
or quit == "no" or quit == "No")
{
break;
}
else
{
cout << "Enter another 2 strings to compare, seperated by a # e.g. \"abc#cba\"." << endl;
}
} //end of while
return 0;
}
//is second a mirror image of first?
bool compareMirrorString(const std::string& first, const std::string& second)
{
if (first.length() != second.length()) {
return false;
}
//put first_string on stack
std::stack<char> stackChar;
for (auto elem : first){
stackChar.push(elem);
}
int size = stackChar.size();
//compare first and second strings
bool compare_equal = true;
for (int i = 0; i < size; i++)
{
if (stackChar.top() == second[i])
{
stackChar.pop();
}
else
{
compare_equal = false;
break;
}
}
return compare_equal;
}
In the first iteration of the loop:
cin >> quit;
will read up to, but not including the newline character.
In the second iteration of the loop:
std::getline(std::cin, input);
will read that newline character, and hence read an empty string into input.
There are several ways to fix this
You could use getline to read the string quit
You could read input again, if it happens to be an empty string.
You could call cin.get(); after the cin >> quit to clear the newline.

How to parse this data of unknown size

I have a simple text file containing instructions per a line. e.g
A 1 1
B 2 1 A
C 3 1 A
D 4 1 B C
Basic syntax is Letter, Num, Num, Letter(s)
I just don't know what function I should be calling to parse the data, and how to parse it in the given syntax. I feel like there's so many ways to do it.
The following C++ example shows one of possible way to read single characters from file, controlling end of line:
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
int main(void)
{
ifstream inpFile("test.txt");
string str;
char c;
while (inpFile.good()) {
// read line from file
getline(inpFile, str);
// make string stream for reading small pieces of data
istringstream is(str);
// read data ingnoring spaces
do
{
is >> c; // read a single character
if (!is.eof()) // after successful reading
cout << c << " "; // output this character
} while (is.good()); // control the stream state
cout << "[End of line]" << endl;
}
cout << "[End of file]" << endl;
}
Here istringstream is used to process single line that is got by getline.
After reading a char with is >> c value in c can be checked for content, e.g.:
if (!is.eof()) // after successful reading
{
// analyze the content
if ( isdigit(c) )
cout << (c - '0') << "(number) "; // output as a digit
else
cout << c << "(char) "; // output as a non-number
}
Note: if file can contain not single characters / digits, but numbers and words, type of c should be appropriate (e.g. string)
In C++, read an entire line and make a stream from it, then read from that stream with >>.
Example:
std::ifstream file(filename);
std::string line;
while (file.getline(line))
{
std::istringstream in(line);
char letter;
int number1;
int number2;
std::vector<char> letters;
if (in >> letter >> number1 >> number2)
{
char letter2;
while (in >> letter2)
{
letters.push_back(letter2);
}
}
}
This is C example that read lines, and then goes (using pointer) from the beginning to output readable characters (with code greater than 32):
#include <stdio.h>
#include <ctype.h>
#define MAX_LINE_LEN 80
int main(void)
{
FILE * inpFile = fopen("test.txt", "r");
char buf[MAX_LINE_LEN];
char *p;
while (!feof(inpFile))
{
// read a line from file
if (fgets(buf, MAX_LINE_LEN, inpFile) != NULL)
{
p = buf; // start from the beginning of line
// reading data from string till the end
while (*p != '\n' && *p != '\0')
{
// skip spaces
while (isspace(*p) && *p != '\n') p++;
if (*p > 32)
{
// output character
printf("%c ", *p);
// move to next
p++;
}
}
}
printf("[End of line]\n");
}
printf("[End of file]\n");
return 0;
}
To extract numbers and words from the line you can do something like:
// reading data from string till the end
while (*p != '\n' && *p != '\0')
{
// skip spaces
while (isspace(*p) && *p != '\n') p++;
if (*p > 32)
{
int num;
char word[MAX_LINE_LEN];
// trying to read number
if (sscanf(p, "%i", &num))
{
printf("%i(number) ", num);
}
else // read string
{
sscanf(p, "%s", word);
printf("%s(string) ", word);
}
// move to next space in the simplest way
while (*p > 32) p++;
}
}

Why is a space in the string making my code loop infinitely? [duplicate]

This question already has answers here:
Infinite loop with cin when typing string while a number is expected
(4 answers)
Closed 6 years ago.
I have the following code which simply takes a string and find each character's index in the alphabet.
void encrypt()
{
string alpha = "abcdefghijklmnopqrstuvwxyz";
string word;
vector<char> temp;
char a, b;
cout << "Enter string to encrypt: \n";
cin >> word;
for (int i=0; i<word.length(); i++)
{
bool t = false;
a = word[i];
for (int j=0; j<alpha.length(); j++)
{
b = alpha[j];
if (a == b)
{
cout << a << "'s index = " << j+1 << endl;
t = true;
}
}
if (t == false)
{
cout << "space here\n";
}
}
}
when i input a word/string with no space the code works fine but when i input a string with a space the program goes into an infinite loop.
edit main() added due to request:
main()
{
int a;
bool b = false;
while (b == false)
{
cout << "1. Encrypt a string\n";
cout << "2. Decrypt a string\n";
cout << "3. Exit\n";
cout << endl;
cin >> a;
cout << endl;
if (a == 1)
{
encrypt();
}
else if (a == 2)
{
decrypt();
}
else if (a == 3)
{
b = true;
}
}
return 0;
}
cin >> word;
will read only the first word and leave the second word in the input stream. After that, the call
cin >> a;
will result in an error unless the second word starts with a number. Once the program enters a state of error, nothing is read and the program stays in a loop.
To diagnose problems like these, always check the state of the stream after a read operation.
if ( cin >> word )
{
// Use word
}
else
{
// Deal with error.
}
if ( cin >> a )
{
// Use a
}
else
{
// Deal with error.
}
To address your real problem, don't use operator>> to read space separated string. Use getline (and use a variable name different from word).
std::string str;
if ( getline(std::cin, str) )
{
// Use str
}
else
{
// Deal with error.
}
However, in order to use getline successfully, you have to make sure that after a is read, you ignore the rest of the line. Otherwise, the rest of the line will be read by getline.
if ( cin >> a )
{
// Ignore rest of the line
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// Use a
}
else
{
// Deal with error.
}
Replace cin >> word; with getline(cin, word);. It will accept a line as input. Which will resolves your input containing spaces.
As far as infinite loop concern, clear the error bits on the stream cin.clear();
You can check whether cin is accepting the space separated string completely, by doing a cout instantly after the cin. If cin is not accepting the space separated string, then try using getline
Issue resolved:
Use the following:
cout << "Enter string to encrypt: ";
scanf(" %[^\n]s",word);
for (int i=0; word[i]!='\0'; i++)
{
use
include <cstdio>
Hope this solves the problem!! I will get back to you with the solution using string..

Error implementing Caesar Cipher Using C++

I am trying to implement Caesar Cipher using C++. The directions are to use this file which is already encrypted:
5
Asi ymj rtrjwfymjx tzylwfgj.
Aqq rnrxd bjwj ymj gtwtlwtajx
Dni ldwj fsi ldrgqj ns ymj bfgj.
Tbfx gwnqqnl fsi ymj xnymjd ytajx
The number 5 represents the shift that is applied to the text. I have to decode the Caesar ciphered text and reverse the lines as in put line 4 in line 1's position and line 3 in line 2's. The first letter of each line does not need to be decoded (the uppercase letters).
The text should look like this after running the program:
Twas brillig and the sithey toves
Did gyre and gymble in the wabe.
All mimsy were the borogroves
And the momerathes outgrabe.
As of right now, I have this code:
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
using namespace std;
char decipher (char c, int shift);
int main(){
//declare variables
char c;
string deciphered = "";
int shift;
vector <string> lines;
//ask for filename and if not found, keep trying
ifstream inFile;
string filename;
cout << "What is the name of the file? ";
cin >> filename;
inFile.open(filename);
while (!inFile){
cout << "File not found. Try again: ";
cin >> filename;
inFile.open(filename);
}
//find shift from file
inFile >> shift;
//get lines from file
inFile >> noskipws;
while (inFile >> c){
char decipheredChar = decipher (c, shift);
deciphered += decipheredChar;
}
cout << deciphered;
}
char decipher (char c, int shift){
string letters = "abcdefghijklmnopqrstuvwxyz";
if (c == 'T'){
return c;
}
else if (c == 'D'){
return c;
}
else if (c == 'A'){
return c;
}
else if (c == ' '){
return c;
}
else {
int currentPosition = letters.find(c);
int shiftedPosition = currentPosition - shift;
if (shiftedPosition < 0){
shiftedPosition = 26 + shiftedPosition;
}
char shifted = letters[shiftedPosition];
return shifted;
}
}
The result I'm getting is this:
uAnd the momerathes outgrabeuuAll mimsy were the borogrovesuDid gyre and gymble in the wabeuuTwas brillig and the sithey tovesu
How do I get rid of the u's and also separate the words by line? I have an idea of reversing the lines using a vector and using a loop counting backwards but I'm not sure how to get to there yet. Please help. Thank you.
To answer your question, the 'u's are the newlines. You read them in and decipher them, so they change and the result is pulled from letters. You should be able to add another case to decipher() to leave newlines alone:
char decipher (char c, int shift){
string letters = "abcdefghijklmnopqrstuvwxyz";
if(c == '\n'){ // do not modify new lines.
return c;
}
else if (c == 'T'){
return c;
}
// ...
}
Probably the cleanest way to reverse the lines is parse them while you read the characters. You can them pop them from the vector in reverse order. A working (but not robust) example would be to add the following to your while loop:
while (inFile >> c){
char decipheredChar = decipher (c, shift);
deciphered += decipheredChar;
if(decipheredChar=='\n'){ //if full line
lines.push_back(deciphered); //push line
deciphered = ""; //start fresh for next line
}
}
lines.push_back(deciphered+'\n'); //push final line (if no newline)
while(!lines.empty()){
cout << lines.back(); //prints last line
lines.pop_back(); //removes last line
}
I say not robust because there are minor things you may still need watch out for. For instance, this reads stores newline from after 5, and if the file ends in a newline I've added an empty one on the end... I'll leave you minor details to clear up.

Counting the number of words in a file

#include <iostream>
#include <string>
#include <fstream>
#include <cstring>
using namespace std;
int hmlines(ifstream &a){
int i=0;
string line;
while (getline(a,line)){
cout << line << endl;
i++;
}
return i;
}
int hmwords(ifstream &a){
int i=0;
char c;
while ((c=a.get()) && (c!=EOF)){
if(c==' '){
i++;
}
}
return i;
}
int main()
{
int l=0;
int w=0;
string filename;
ifstream matos;
start:
cout << "give me the name of the file i wish to count lines, words and chars: ";
cin >> filename;
matos.open(filename.c_str());
if (matos.fail()){
goto start;
}
l = hmlines(matos);
matos.seekg(0, ios::beg);
w = hmwords(matos);
/*c = hmchars(matos);*/
cout << "The # of lines are :" << l << ". The # of words are : " << w ;
matos.close();
}
The file that i am trying to open has the following contents.
Twinkle, twinkle, little bat!
How I wonder what you're at!
Up above the world you fly,
Like a teatray in the sky.
The output i get is:
give me the name of the file i wish to count lines, words and chars: ert.txt
Twinkle, twinkle, little bat!
How I wonder what you're at!
Up above the world you fly,
Like a teatray in the sky.
The # of lines are :4. The # of words are : 0
int hmwords(ifstream &a){
int i;
You've forgotten to initialize i. It can contain absolutely anything at that point.
Also note that operator>> on streams skips whitespace by default. Your word counting loop needs the noskipws modifier.
a >> noskipws >> c;
Another problem is that after you call hmlines, matos is at end of stream. You need to reset it if you want to read the file again. Try something like:
l = hmlines(matos);
matos.clear();
matos.seekg(0, ios::beg);
w = hmwords(matos);
(The clear() is necessary, otherwise seekg has no effect.)
Formatted input eats whitespaces. You can just count tokens directly:
int i = 0;
std::string dummy;
// Count words from the standard input, aka "cat myfile | ./myprog"
while (cin >> dummy) ++i;
// Count files from an input stream "a", aka "./myprog myfile"
while (a >> dummy) ++i;