I am making a D&D game in C++. I roll 6 scores randomly, put them in a vector, display them to the player. Then I go through each ability (str, dex, con, int, wis, and cha), and call a function that asks the player which of the scores they want to use for each ability, and then i remove it from the vector, return the value, and move on to the next ability. It works fine unless there is a duplicate roll, in which case it deletes both of the duplicates. I want it to only remove one at a time regardless of duplicates, and I haven't been able to find anything online to do this. Here is the function call
int Character::initScores(std::vector<int> & v, std::string ability)
{
int c = 0;
bool error = 0;
do {
if (c != 0) {
std::cout << "That isn't one of your scores. Try again. " <<
std::endl;
}
int choice;
std::cout << ability << ": ";
std::cin >> choice;
if (std::find(v.begin(), v.end(), choice) != v.end())
{
v.erase(std::remove(v.begin(), v.end(), choice), v.end());
std::cout << "Your remaining rolls are ";
for (int i = 0; i < v.size(); i++)
std::cout << v[i] << " ";
std::cout << std::endl;
return choice;
}
else
{
c++;
error = 1;
}
} while (error = 1);
}
And the function calls
std::cout << "Enter which score you want for... " << std::endl;
strength = initScores(scores, "Strength");
dexterity = initScores(scores, "Dexterity");
constitution = initScores(scores, "Constitution");
intelligence = initScores(scores, "Intelligence");
wisdom = initScores(scores, "Wisdom");
charisma = initScores(scores, "Charisma");
Also please lmk if there is anything inefficient/bad practice in my code, I have only recently started working on my own coding projects
You are calling std::remove(), which "removes" ALL matching values from the container (really, it just moves them to the end of the container), and then you are calling the 2-parameter overload of the erase() method to physically delete ALL of the "removed" values from the container.
If you just want to remove 1 element, pass the iterator returned by std::find() to the 1-parameter overload of the erase() method:
auto iter = std::find(v.begin(), v.end(), choice);
if (iter != v.end())
{
v.erase(iter);
...
}
Related
I have the following simple code. I declare a vector and initialize it with one value 21 in this case. And then i am trying to find that value in the vector using find. I can see that the element "21" in this case is in the vector since i print it in the for loop. However why the iterator of find does not resolve to true?
vector<uint8_t> v = { 21 };
uint8_t valueToSearch = 21;
for (vector<uint8_t>::const_iterator i = v.begin(); i != v.end(); ++i){
cout << unsigned(*i) << ' ' << endl;
}
auto it = find(v.begin(), v.end(), valueToSearch);
if ( it != v.end() )
{
string m = "valueToSearch was found in the vector " + valueToSearch;
cout << m << endl;
}
are you sure it doesn't work?
I just tried it:
#include<iostream> // std::cout
#include<vector>
#include <algorithm>
using namespace std;
int main()
{
vector<uint8_t> v = { 21 };
uint8_t valueToSearch = 21;
for (vector<uint8_t>::const_iterator i = v.begin(); i != v.end(); ++i){
cout << unsigned(*i) << ' ' << endl;
}
auto it = find(v.begin(), v.end(), valueToSearch);
if ( it != v.end() )
{// if we hit this condition, we found the element
string error = "valueToSearch was found in the vector ";
cout << error << int(valueToSearch) << endl;
}
return 0;
}
There are two small modifications:
in the last lines inside the "if", because you cannot add directly a
number to a string:
string m = "valueToSearch was found in the vector " + valueToSearch;
and it prints:
21
valueToSearch was found in the vector 21
while it's true that you cannot add a number to a string, cout
support the insertion operator (<<) for int types, but not uint8_t,
so you need to convert it to it.
cout << error << int(valueToSearch) << endl;
This to say that the find is working correctly, and it is telling you that it found the number in the first position, and for this, it != end (end is not a valid element, but is a valid iterator that marks the end of your container.)
Try it here
Here is my code using STL library, where I try inserting a node at the end, in the middle and in front. For inserting in the middle, I want to provide insertion after a specific node, and not by incrementing the iterator by 2, as I might not know what to increment it by if it is a long list,
Kindly help why is find function not working:
#include <iostream>
#include <list>
#include <string>
using namespace std;
void printlist(list<int> l)
{
list<int>::iterator it = l.begin();
for (it; it != l.end(); ++it)
{
cout << "printlist function call list items: " << *it << endl;
}
}
int main()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
list<int>::iterator it = l.begin();
cout << 1 << endl;
printlist(l);
l.push_front(0);
cout << 2 << endl;
printlist(l);
it = l.find(l.begin(), l.end(), 2);
l.insert(it, 25);
cout << 3 << endl;
printlist(l);
return 0;
}
Thanks...
std::list<> doesn't have a find() method. You can use the standard algorithm std::find() declared in <algorithm>:
it = std::find(l.begin(), l.end(), 2);
See the answer by #0x499602D2.
But to elaborate on an important point raised in a comment by #NeilKirk, you wrote:
void printlist(list<int> l)
{
list<int>::iterator it = l.begin();
for (it; it != l.end(); ++it)
{
cout << "printlist function call list items: " << *it << endl;
}
}
Note that you are passing the list l by value, not by reference. Passing a class by value (that has not been designed to use implicit sharing) will make a copy. Thus, l will be a copy of the parameter passed. If your list contained a million elements, then passing it by value will make a million-element-copy. You can fix that with:
void printlist(list<int> & l) { ... }
Or if you don't plan on making any changes, it's always nice to announce that with:
void printlist(list<int> const & l) { ... }
Also, C++11 has a range-based for which does the iterator begin/end stuff under the hood for you, and automatic variable typing:
void printlist(list<int> const & l)
{
for (auto i : l)
{
cout << "printlist function call list items: " << i << endl;
}
}
Lots of ways to get fancy in that spirit. But the more critical thing is not go making copies of your data structures, passing them by value when you don't need to!
Complete program here for clarification: compilr.com/rayd360/test-songs
struct Album {
string title;
string year;
string track;
vector<string> tracks;
vector<string>::iterator trk;
}MyAlbums[40];
cout << "\n list of songs: \n";
for(int i = 0; i < 5; i++){
cout << *MyAlbums[i].trk << "\n";
}
gives me: "bash: line 1: 232 Segmentation fault "
I am needing to pass the list of tracks to a function that sorts them alphabetically then prints them out.
Any help is very much appreciated!
The line in the for loop dereferences the iterator returned by MyAlbums[i].trk. Since the iterator isn't assigned to anything (its internal pointer isn't pointing to anything) dereferencing it is Undefined Behavior. This can also cause a segmentation fault since your looking into memory that isn't owned by you.
To fix this I think you should remove the iterator from your class. Instead, use one inside your loop:
auto begin = MyAlbums.tracks.begin(),
end = MyAlbums.tracks.end();
for (auto it = begin; it != end; ++it)
{
std::cout << *it;
}
struct Album {
string title;
string year;
string track;
}MyAlbums[40];
vector<Album> tracks;
vector<Album>::iterator trk=tracks.begin();
cout << "\n list of songs: \n";
for(trk;trk!=tracks.end();++trk){
cout << *trk.title << "\n";
Assuming that you have already populated the MyAlbums.
There are two problems:
1) MyAlbums[i].trk is not initialized
2) While iterating through containers, ensure not to access container with invalid index.
(Ex: for(int i = 0; i < 5; i++) is not safe in this context).
// C++11
for ( auto track : MyAlbums.tracks )
{
cout << track << "\n";
}
// If not C++11
for (vector<string>::Iterator trackItr = MyAlbums.begin();
trackItr != MyAlbums.end(); ++trackItr)
{
cout << *trackItr << "\n";
}
My remove_if seems to be overwriting the elements that are not filtered out with values of filtered out elements. The purpose of these code is to allow user to filter and display only teacher from a certain category. (Not deleting any element)
Here are some of the code
static string compare;
static string debug;
bool filter_Cat (Teacher &t)
{
return (t.getCat() != compare);
}
void filterCat (vector<Teacher> &t)
{
vector<Teacher>::iterator i;
vector<Teacher>::iterator newedited = remove_if(t.begin(), t.end(), filter_Cat);
for (i = t.begin(); i != newedited; ++i)
{
Teacher& te = *i;
te.getName();
cout << "\t";
te.getCategory();
cout << "\t";
te.getLocation();
}
}
void filterTutorCat(vector<Teacher> &t)
{
int choice;
cout << "No\tCategory" << endl
<< "1\tEnglish" << endl
<< "2\tMath" << endl
<< "3\tScience" << endl
<< "Choose the category you wish to filter :";
cin >> choice;
getline(cin, debug);
if(choice <= 3 && choice > 0)
{
if (choice == 1)
{
compare = "English";
filterCat(t);
}
if (choice == 2)
{
compare = "Math";
filterCat(t);
}
if (choice == 3)
{
compare = "Science";
filterCat(t);
}
}
else
{
cout << "Invalid Option" << endl;
}
}
remove_if shifts elements, for which the compare function returns false, from right to left; which in other words means, it overwrites the elements, for which compare returns true, with elements, for which compare returns false. The size of the vector doesn't change, however.
This reads,
Removes all elements satisfying specific criteria from the range [first, last). The first version removes all elements that are equal to value, the second version removes all elements for which predicate p returns true.
Removing is done by shifting the elements in the range in such a way that elements to be erased are overwritten. The elements between the old and the new ends of the range have unspecified values. Iterator to the new end of the range is returned. Relative order of the elements that remain is preserved.
So what you want to do should be expressed as:
void filterCat (vector<Teacher> &v)
{
for (vector<Teacher>::iterator it = v.begin(); it != v.end() ; ++it)
{
if (!filter_Cat(*i))
{
std::cout << i->getName() <<"\t" << i->getCategory() << std::endl;
}
}
}
It seems in your code, getName() prints the name which ideally it should not do, instead it should return name. So I would suggest you to change it to make it return name. And do the same for getCategory as well. Choose your name correctly. If it is getName(), you should get you name by returning it; if it is printName(), then it should print name.
Also, the code which you've written isn't good:
You should avoid global variables.
You should avoid if-else as much as possible. Learn better ways.
You should learn about function objects (or functor)
You should learn about const member function.
You should understand the difference between iterator and const_iterator, and their usage.
You should understand the difference between const reference, and non-const reference. And try using them appropriately.
So I would write your code as:
//this is functor, not a function
struct filter_cat
{
std::string m_cat; //use member data, avoid global variable
filter_cat(std::string const & cat) : m_cat(cat) {}
bool operator()(Teacher const & t) const //const member function
{
return (t.getCat() != m_cat); //getCat should be const member function
}
};
//pass vector by const reference
void filterCat (vector<Teacher> const & v, filter_cat filter)
{
//use const_iterator here, instead of iterator
for (vector<Teacher>::const_iterator it = v.begin(); it != v.end() ; ++it)
{
if (!filter(*i))
{
//getName and getCategory should be const member function
std::cout << i->getName() <<"\t" << i->getCategory() << std::endl;
}
}
}
void filterTutorCat(vector<Teacher> const &t)
{
int choice;
cout << "No\tCategory" << endl
<< "1\tEnglish" << endl
<< "2\tMath" << endl
<< "3\tScience" << endl
<< "Choose the category you wish to filter :";
cin >> choice;
getline(cin, debug);
//avoid if-else as much as possible, learn better ways!
std::string cats[] = {"English", "Math", "Science"};
if(choice <= 3 && choice > 0)
{
filterCat(v, filter_cat(cats[choice-1]));
}
else
{
cout << "Invalid Option" << endl;
}
}
As noted in the comments: getCat, getName and getCategory should be const member functions. In fact, if getCategory returns category, then getCat isn't even needed.
Solved my issue.
remove_if collects the values for which filter_Cat returns false at the start of the container. While it doesn't reduce the number of elements in the container it neither does make any guarantees about the values of the elements beyond the returned range. So you are loosing values when using remove_if.
I am working on a Polynomial class which uses the STL linked list. One of the functions requires me to add two Polynomial's together. For some reason, the += operator seems to be duplicating the node, as opposed to merely modifying the contents.
Here is the class declaration:
class Polynomial
{
public:
Polynomial(pair<double,int>); //Specified constructor
void add(const Polynomial&);
void print();
private:
Polynomial(); //Default constructor
list<pair<double,int> > terms;
};
This is the add member function:
void Polynomial::add(const Polynomial& rhs)
{
list<pair<double,int> >::const_iterator r;
list<pair<double,int> >::iterator l;
for(r=rhs.terms.begin(); r!=rhs.terms.end(); r++)
{
bool match=0;
//Check to see if we have an existing nth order node
for(l=terms.begin(); l!=terms.end(); l++)
{
//If we do, just add the coefficients together
if(l->second == r->second)
{
l->first += r->first;
match = 1;
}
}
//If there was no matching existing node, we need to find out
//where to insert it into the list.
if(!match)
{
l=terms.begin();
bool inserted=0; //Sentinel for the loop
while(l!=terms.end() && !inserted)
{
//If there's only one term in the list
//Just compare and stick it in front or behind the existing node
if(terms.size()==1)
{
int this_exp = l->second;
int exp_to_ins = r->second;
if(exp_to_ins > this_exp) terms.push_back((*r));
if(exp_to_ins < this_exp) terms.push_front((*r));
inserted = 1;
}
//If there's more than one node, we need to traverse the list
if(terms.size()>1)
{
if(l!=terms.begin())
{
int this_exp = l->second;
l++;
int next_exp = l->second;
int exp_to_ins = r->second;
//If the new node value is between the current and next node
//Insert between them.
if((this_exp < exp_to_ins) && (exp_to_ins < next_exp))
{
terms.insert(l,(*r));
inserted = 1;
}
}
else if(l==terms.begin())
{
int this_exp = l->second;
int exp_to_ins = r->second;
//This will be the smallest order node
//Put it in the top spot
if(this_exp > exp_to_ins)
{
terms.push_front((*r));
inserted = 1;
}
l++;
}
}
}
//If we've traversed the list and can't find the right place
//this must be the greatest order node in the list
//so just tack it on the end.
if(!inserted) terms.push_back((*r));
}
}
}
Works fine with ordering the nodes in the correct order, but we have an existing nth order node, rather than just adding the coefficients together, it keeps the original node but seems to make a second node with the coefficients added together, and I have no idea why.
If I run the print function, for what should be F(x) = -2x^7 + 3x^6 - 11x^5 - 2x^4, instead I get F(x) = -2x^7 + 3x^6 - 11x^5 - 10x^5. If I call the size() function on the list, I get 4. But if I run the following code to print out the info from the nodes in the list:
stringstream test;
for(i=terms.end(); i!=terms.begin(); i--)
{
test << "Coefficient: " << i->first << " ";
test << "Exp: " << i->second << endl;
}
cout << "Size: " << terms.size() << endl;
cout << test.str();
The following is output:
Coefficient: -10 Exp: 5
Coefficient: -2 Exp: 7
Coefficient: 3 Exp: 6
Coefficient: -11 Exp: 5
Any help greatly appreciated.
EDIT: This is the test program.
Polynomial p(pair<double, int>(-10, 5));
p.add(Polynomial(pair<double,int> (-2,4)));
p.add(Polynomial(pair<double,int> (3,6)));
p.add(Polynomial(pair<double,int> (-2,7)));
p.add(Polynomial(pair<double, int> (-1,5)));
Your add() function seems to be correct except the print:
for(i=terms.end(); i!=terms.begin(); i--)
{
test << "Coefficient: " << i->first << " ";
test << "Exp: " << i->second << endl;
}
This is completely wrong, and invokes undefined behavior. i is initially terms.end() and you've dereferencing it? items.end() returns past-the-end iterator. Even if I assume it correct for a while, the condition i!=terms.begin() means the first element is never printed!
So the fix is this:
for(list<pair<double,int> >::iterator i=terms.begin(); i!=terms.end(); i++)
{
test << "Coefficient: " << i->first << " ";
test << "Exp: " << i->second << endl;
}
And it prints expected output:
Size: 4
Coefficient: -2 Exp: 4
Coefficient: -11 Exp: 5
Coefficient: 3 Exp: 6
Coefficient: -2 Exp: 7
Is it not correct?
See the output yourself here also : http://www.ideone.com/p8mwJ
By the way, instead of add, you could make it operator+= instead, as:
const Polynomial& operator+=(const Polynomial& rhs)
{
//same code as before
return *this;
}
If you write so, then you can add polynomials as:
Polynomial p(pair<double, int>(-10, 5));
p += Polynomial(pair<double,int> (-2,4));
p += Polynomial(pair<double,int> (3,6));
p += Polynomial(pair<double,int> (-2,7));
p += Polynomial(pair<double, int> (-1,5));
Demo : http://www.ideone.com/aA1zF
I just read your comment, and came to know that you want to print it in reverse order, in that case, you could use rbegin() and rend() instead of begin() and end() as:
for(list<pair<double,int> >::const_reverse_iterator i=terms.rbegin();
i!=terms.rend();
i++)
{
test << "Coefficient: " << i->first << " ";
test << "Exp: " << i->second << endl;
}
I would also advice you to make print a const function as :
void print() const
//^^^^ this makes the function const!
Better yet overload operator<< .
Anyway reverse order printing demo : http://www.ideone.com/Vk6XB
Your test loop (the one printing in the stringstream) is incorrect: it's undefined behavior to dereference the end () iterator. Probably your "std::list" is implemented in a circular way (i.e. with begin == end+1) so dereferencing "end" gives you *begin in your test loop.
Use reverse iterators to print the list in reverse order:
for (i = list.rbegin (); i != list.rend (); ++i)
{
test << "Coefficient: " << i->first ; // etc.
}
Besides the problem pointed out by #Nawaz, there is also a problem in the Polynomial::add function.
If the if(terms.size()==1) block is executed, a new item is inserted in the list. But that increases the size of the list by one, so the if(terms.size()>1) block will also be executed. And this can insert the same node once more.
A bit further in the while loop, you increment l, and proceed using the next node, without checking whether it's valid (ie. without comparing to terms.end()).
There might be more such mistakes, but these came up after a cursory glance.