Vector out of range- C++ - c++

I'm working on a project that requires me to make a game of Hangman in c++. I have most of it working, but I'm stuck at printing out the part of the word guessed correctly each time after the user enters a guess. I've created a class to represent a game of hangman, and in this class are the methods that determine what to do for a guess. If a guess is found in any location in the randomly chosen word from a dictionary, I save that char to the same location in a vector called currentWord. currentWord is initialized in the constructor to be contain "_" for the length of the randomly chosen word(that way it is the same size as the word and I can just update it as the user types a guess). For example, if the word is "semicolonialism", and the user's first guess is 'i', I want to replace the '_' in the currentWord vector with the letter 'i'.
string tempWord = word;
for (int i = 0; i < tempWord.size(); i++) {
u_long location = tempWord.find(guess);
currentWord->at(location) = tempWord[location];
tempWord[location] = '_';
}
What I've tried to do is store the member variable "word" in a temporary variable called tempWord. Then I iterate from 0 to the length of tempword. I use tempWord.find(guess) to find the location in tempWord that is a match for the guess, store that into a variable called location, and then update the currentWord vector at that location to equal the tempWord at that location. Since this would only work for the first time the matching char is found, I then change tempWord[location] to '_', that way the next time through, location will be different. But by doing this I some times get the out of range error. If I comment out
tempWord[location] = '_';
then I don't see this error, but only the first occurrence is replaced. Even though I get this out of bounds error, I can see in the debugger that each occurrence is properly replaced in the currentWord vector. This leaves me very confused, so any help would be greatly appreciated! Thanks
EDIT
Thanks to rapptz suggestion to check if location is equal to std::string::npos, I finally have it working. Here's the updated code segment with that check in place:
string tempWord = word;
for (int i = 0; i < tempWord.size(); i++) {
u_long location = tempWord.find(guess);
if (location != std::string::npos) {
currentWord->at(location) = tempWord[location];
tempWord[location] = '_';
}
}
I really liked Tristan's suggestion too, and will do that tomorrow most likely. Once I do, I'll post the updated code as well in case someone else might find it useful. Thanks again!

Was going to post this as a comment but it's easier in a bigger text box! You can avoid both the tempWord copy and the for loop like this:
std::string::size_type location = 0, start_pos = 0; // int would be fine, tbh
while ( (location = word.find(guess, start_pos)) != std::string::npos) {
currentWord.at(location) = word[location];
start_pos = location;
}

My guess is that tempword.find(guess) starts from 1 to the lenght of word, not 0. Please share that function too.

Related

Add character at every vowel

I had a similiar question earlier and managed to solve it. Now I'm trying to do it backwards, this is my code at the moment.
Basically I want the end result to "p5A5SSW5o5R5o5d", but when I run this it just find the first "o" which is in the first part of passworod, I need it to skip the the vowel it have already added a 5 before and after to.
I want every vowel (only included AOao in the current string as thats all the vowels appearing in my string), to have a 5 as prefix and suffix. It gets stuck at the first o and doesnt proceed to the next o. I have created a nested for loop which means it takes the first character in the encrypt-string, proceeds to the next for-loop and loops through every single vowel Ive included in the vowel string until it finds a match. Otherwise it restarts at the first for-loop but incremented by one. First go it should search the letter "p", second run it should search the letter "A" and so on.
Result: p5A5SSW55o55rod
Expected Result: p5A5SSW5o5R5o5d
In the end I will also want to rotate all the characters, but thats for another task, I think I can just use either if-statement or a switch to do that. If it ends up on a 5, do nothing, otherwise rotate.
I hope I made myself clear and provided you with all the relevant information, otherwise just holler in the comments.
Thanks in advance.
#include <string>
#include <sstream>
const string vowel = "AOao";
string encrypt, decrypt;
encrypt = "pASSWoRod";
decrypt = encrypt;
for (int i=0; i<encrypt.length(); i++){
for (int j=0; j<vowel.length(); j++){
if (encrypt[i] == vowel[j]){
decrypt.insert(decrypt.find(vowel[j]), 1, '5');
decrypt.insert(decrypt.find(vowel[j]) + 1, +1, '5');
}
}
}
return decrypt;
}
find, when not proved a starting point, always finds the first instance.
Searching and keeping track of the string length and where you've already inserted characters is much harder than it seems at first glance (as you've noticed).
Build the result from scratch instead of inserting characters into an initial string.
Also, implement this function (actual implementation left as an exercise):
bool is_vowel(char c);
and then
std::string encrypt = "pASSWoRod";
std::string decrypt;
for (auto c: encrypt)
{
if (is_vowel(c))
{
decrypt += '5';
decrypt += c;
decrypt += '5';
}
else
{
decrypt += c;
}
}
find with one argument starts from the beginning. You would need the other find.
However maintaing an index in decrypt, one no longer would need a find.
int encryptI = 0;
for (int i = 0; i < decrypt.length(); i++, encyptI++){
for (int j=0; j<vowel.length(); j++){
if (decrypt[i] == vowel[j]){
//encryptI = encrypt.find(vowel[j], encryptI);
encrypt.insert(encryptI, 1, '5');
encrypt.insert(encryptI + 1, +1, '5');
encryptI += 2;
break;
}
}
}
The code could be nicer.
string encrypt;
for (int i = 0; i < decrypt.length(); i++, decyptI++){
char ch = decrypt[i];
if (vowel.find(ch) != string::npos) {
encrypt.append('5');
encrypt.append(ch);
encrypt.append('5');
} else {
encrypt.append(ch);
}
}
Your find is stopping at the first instance of the vowel, 'o'. You should include a starting position for your find so you ignore the part of the decrypt string you've already analyzed.

How to interpret a GDB backtrace in order to determine a segmentation fault?

I am in an entry level programming class. I understand that segmentation faults are due to an error in memory storage somewhere along the way. The program I wrote is supposed to take a file given to us that is in code and contains the instructions to decode it and then print the decoded message.
We have several test cases and my code runs for some of them, but not for the last one. I learned about GDB for debugging for the first time today and used backtrace full to try and source the error, but I'm not totally sure how to interpret what it gave me.
This is the code that I wrote.
**edited code out
When I did a backtrace, this is what it told me.
#2 0x0000000000401523 in main () at main.cpp:42
second = 61 '='
third = 72 'H'
msg = 0x606308
i = 8
chars = ""
first = 90 'Z'
numMess = 8
out = <incomplete type>
name = "input4.txt"
in = <incomplete type>
arr = "IJKLMNOPQRSTUVWXYZABCDEFGH"
I don't know what the backtrace is telling me and I'm unsure of what to do with that information to discover and fix my error.
The hints from the trace are
i = 8
chars = ""
numMess = 8
i equals numMess and chars is empty.
Why does this matter? Looking at where numMess comes from, we see it's used to size the dynamic array pointed at by msg and msg is later indexed by i. when i equals numMess, msg[i] is out of bounds.
So how did this happen?
string chars;
getline(in, chars); // this doesn't make much sense. Reconsider it
for (chars; std::getline(in, chars); i < numMess) {
Here's where things went wrong. A for loop should look something like
for (initializer ; exit condition ; iterator to advance the loop)
but
for (chars; // does nothing. Compiler may be warning you
std::getline(in, chars); // loop exits on failed read
i < numMess) { // does nothing to advance the loop
Nothing in here prevents i from exceeding numMess because i < numMess is not being used as an exit condition. Right, but why doesn't std::getline(in, chars); kick out of the loop when it hits the end of the file? Empty line at the end of the file. chars was successfully set to an empty string.
for ( ; // nothing happening here
i < numMess and std::getline(in, chars) ;
++i) { // and remove the ++i from the end of the loop.
Gets you out of the current predicament and might give you a program that does what you want. Haven't tested it.
But what if there is a mistake in the file and the file exits before reaching numMess? This is the sort of stuff PaulMckenzie is getting at. You're better off not trusting numMess at all and going with something more like
int numMess = 0;
in >> numMess; // not that we care
vector<Messages> msg;
//read file
int i = 0;
string chars;
while (std::getline(in, chars)) { // get each line. Note this gets the empty line, too
Messages temp; // make a temporary for storage
temp.messageNum = i + 1;
temp.codedMessage = chars;
//decode file
for (char &j : chars) {
if (j - 'A' >= 0 && j - 'A' < 26)
j = arr[j - 'A'];
}
temp.decodedMessage = chars;
msg.push_back(temp);
}

member function erase() not working in a loop

I'm programming a little game; but stringname.erase() seems to be not working in a 'for-loop' , I want to understand why, I have other alternatives, but I don't understand what's going on in the following code.
More explications of my situation (Important!):
guess is a char.
'tmgword' and 'word' are of type string, and: tmgword = word ;
what I understand from my code:
in the first time,the 'while'-loop verifies if there is 'guess' in the string 'tmpgword'.
That is true and the for-loop is working fine, the right character(guess) that verifies the if-condition is erased.
in the second time: the 'while'-loop verifies again if there is 'guess' in the string 'tmpgword'.
that is true, and hence we go into the 'for-loop' again; and then into the 'if'-block ( the right char is found ) but here erase() don't work, and we enter in an infinite loop.
when the program finds the right index using 'for-loop', I break, and I start the search from the beginning in case there are more occurrences of guess.
the problem is: the program finds 'guess' again but erase() won't delete it!
can someone explain please. Here is my code:
while (tmpgword.find(guess,0) != string::npos )
{
for (i = 0; i < word.size(); i++) // verify the input;
{
if (word[i] == guess)
{
encword[i] = word[i];//I don't think this line is important
tmpgword.erase(tmpgword.begin() + i);
break;
}
}
}
After you do the first erase, the character positions in tmpgword are not the same as in word.
string::find() returns the position of the element when it's found, so you can use that instead of looping through word.
size_t pos = 0;
while ((pos = tmpgword.find(guess, pos)) != string::npos) {
tmpgword.erase(pos, 1);
}
I've used pos as the starting position for each call to find() so it starts from where it just erased, rather than searching from the beginning each time through (there can't be any occurrences before that, because they've all been erased).

Finding all occurrences using rfind, flow challenges?

Following a c++ tutorial and teaching about find() the following code was implemented to search for all the "cat" occurrences in a string:
std::string input;
std::size_t i = 0, x_appearances = 0;
std::getline(std::cin,input);
for(i = input.find("cat",0); i != std::string::npos; i=input.find("cat", i))
{
++x_appearances;
++i; //Move past the last discovered instance to avoid finding the same string
}
Then the tutorial challenges the apprentice to change find() for rfind(), and that's where the problems came in, first I tried what seemed to be the obvious approach:
for(i = input.rfind("cat",input.length()); i != std::string::npos; i=input.rfind("cat", i))
{
++x_appearances;
--i; //Move past the last discovered instance to avoid finding the same string
}
but with this solution I fell into an infinite loop. Then I discovered that it was happening because the increment is performed before the condition check, and that the increment rfind() was always finding a match even with i==std::string::npos (if the match is on the beginning of the string, for example "cats"). My final solution came to be:
int n=input.length();
for(i = input.rfind("cat",input.length()); n>0 && i!=std::string::npos; i=input.rfind("cat", i))
{
++x_appearances;
n=i;
--i; //Move past the last discovered instance to avoid finding the same string
}
With n I can keep the track of the position in the string, and with it exit the for loop when the entire string had been searched.
So my question is: Is my approach correct? Did I need an extra variable or is there any other simpler way of doing this?
for(i = input.rfind("cat",input.length()); i != std::string::npos; i=input.rfind("cat", i))
{
++x_appearances;
--i; //Move past the last discovered instance to avoid finding the same string
}
The problem with the above is the --i inside the loop. Suppose the input string starts with "cat". Your algorithm will eventually find that "cat" with i being 0. Since you've declared i as a std::size_t, subtracting 1 from 0 results in the largest possible std::size_t. There's no warning, no overflow, no undefined behavior. This is exactly how unsigned integers must work, per the standard.
Somehow you need to handle this special case. You could use an auxiliary variable and a more convoluted test in your loop. An alternative is to keep your code simple and at the same time make it blatantly obvious you are explicitly handling this special case:
for (i = input.rfind("cat"); i != std::string::npos; i=input.rfind("cat", i-1))
{
++x_appearances;
// Finding "cat" at the start means we're done.
if (i == 0) {
break;
}
}
Note also that I've changed the loop statement a bit. The default value for pos is std::string::npos, which means search from the end of the string. There's no need for that second argument with the initializer. I also moved the --i into the update part of the for loop, changing input.rfind("cat",i) to input.rfind("cat",i-1). Since i is always positive at this point, there's no danger in subtracting one.

Adapting Boyer-Moore Implementation

I'm trying to adapt the Boyer-Moore c(++) Wikipedia implementation to get all of the matches of a pattern in a string. As it is, the Wikipedia implementation returns the first match. The main code looks like:
char* boyer_moore (uint8_t *string, uint32_t stringlen, uint8_t *pat, uint32_t patlen) {
int i;
int delta1[ALPHABET_LEN];
int *delta2 = malloc(patlen * sizeof(int));
make_delta1(delta1, pat, patlen);
make_delta2(delta2, pat, patlen);
i = patlen-1;
while (i < stringlen) {
int j = patlen-1;
while (j >= 0 && (string[i] == pat[j])) {
--i;
--j;
}
if (j < 0) {
free(delta2);
return (string + i+1);
}
i += max(delta1[string[i]], delta2[j]);
}
free(delta2);
return NULL;
}
I have tried to modify the block after if (j < 0) to add the index to an array/vector and letting the outer loop continue, but it doesn't appear to be working. In testing the modified code I still only get a single match. Perhaps this implementation wasn't designed to return all matches, and it needs more than a few quick changes to do so? I don't understand the algorithm itself very well, so I'm not sure how to make this work. If anyone can point me in the right direction I would be grateful.
Note: The functions make_delta1 and make_delta2 are defined earlier in the source (check Wikipedia page), and the max() function call is actually a macro also defined earlier in the source.
Boyer-Moore's algorithm exploits the fact that when searching for, say, "HELLO WORLD" within a longer string, the letter you find in a given position restricts what can be found around that position if a match is to be found at all, sort of a Naval Battle game: if you find open sea at four cells from the border, you needn't test the four remaining cells in case there's a 5-cell carrier hiding there; there can't be.
If you found for example a 'D' in eleventh position, it might be the last letter of HELLO WORLD; but if you found a 'Q', 'Q' not being anywhere inside HELLO WORLD, this means that the searched-for string can't be anywhere in the first eleven characters, and you can avoid searching there altogether. A 'L' on the other hand might mean that HELLO WORLD is there, starting at position 11-3 (third letter of HELLO WORLD is a L), 11-4, or 11-10.
When searching, you keep track of these possibilities using the two delta arrays.
So when you find a pattern, you ought to do,
if (j < 0)
{
// Found a pattern from position i+1 to i+1+patlen
// Add vector or whatever is needed; check we don't overflow it.
if (index_size+1 >= index_counter)
{
index[index_counter] = 0;
return index_size;
}
index[index_counter++] = i+1;
// Reinitialize j to restart search
j = patlen-1;
// Reinitialize i to start at i+1+patlen
i += patlen +1; // (not completely sure of that +1)
// Do not free delta2
// free(delta2);
// Continue loop without altering i again
continue;
}
i += max(delta1[string[i]], delta2[j]);
}
free(delta2);
index[index_counter] = 0;
return index_counter;
This should return a zero-terminated list of indexes, provided you pass something like a size_t *indexes to the function.
The function will then return 0 (not found), index_size (too many matches) or the number of matches between 1 and index_size-1.
This allows for example to add additional matches without having to repeat the whole search for the already found (index_size-1) substrings; you increase num_indexes by new_num, realloc the indexes array, then pass to the function the new array at offset old_index_size-1, new_num as the new size, and the haystack string starting from the offset of match at index old_index_size-1 plus one (not, as I wrote in a previous revision, plus the length of the needle string; see comment).
This approach will report also overlapping matches, for example searching ana in banana will find b*ana*na and ban*ana*.
UPDATE
I tested the above and it appears to work. I modified the Wikipedia code by adding these two includes to keep gcc from grumbling
#include <stdio.h>
#include <string.h>
then I modified the if (j < 0) to simply output what it had found
if (j < 0) {
printf("Found %s at offset %d: %s\n", pat, i+1, string+i+1);
//free(delta2);
// return (string + i+1);
i += patlen + 1;
j = patlen - 1;
continue;
}
and finally I tested with this
int main(void)
{
char *s = "This is a string in which I am going to look for a string I will string along";
char *p = "string";
boyer_moore(s, strlen(s), p, strlen(p));
return 0;
}
and got, as expected:
Found string at offset 10: string in which I am going to look for a string I will string along
Found string at offset 51: string I will string along
Found string at offset 65: string along
If the string contains two overlapping sequences, BOTH are found:
char *s = "This is an andean andeandean andean trouble";
char *p = "andean";
Found andean at offset 11: andean andeandean andean trouble
Found andean at offset 18: andeandean andean trouble
Found andean at offset 22: andean andean trouble
Found andean at offset 29: andean trouble
To avoid overlapping matches, the quickest way is to not store the overlaps. It could be done in the function but it would mean to reinitialize the first delta vector and update the string pointer; we also would need to store a second i index as i2 to keep saved indexes from going nonmonotonic. It isn't worth it. Better:
if (j < 0) {
// We have found a patlen match at i+1
// Is it an overlap?
if (index && (indexes[index] + patlen < i+1))
{
// Yes, it is. So we don't store it.
// We could store the last of several overlaps
// It's not exactly trivial, though:
// searching 'anana' in 'Bananananana'
// finds FOUR matches, and the fourth is NOT overlapped
// with the first. So in case of overlap, if we want to keep
// the LAST of the bunch, we must save info somewhere else,
// say last_conflicting_overlap, and check twice.
// Then again, the third match (which is the last to overlap
// with the first) would overlap with the fourth.
// So the "return as many non overlapping matches as possible"
// is actually accomplished by doing NOTHING in this branch of the IF.
}
else
{
// Not an overlap, so store it.
indexes[++index] = i+1;
if (index == max_indexes) // Too many matches already found?
break; // Stop searching and return found so far
}
// Adapt i and j to keep searching
i += patlen + 1;
j = patlen - 1;
continue;
}