Use of STL algorithms in a tile matching game logic - c++

I have made a tile matching game. It has a Board object that holds a vector of card objects and delegates events to them. Board event handling has following code in it:
// Counting logic-driving card states
int cardFaceUpCounter = 0;
std::vector<Card*> faceUpCards(2);
// Checking for logic-driving card states
for (auto& card : this->boardMatrix)
{
if (this->isCardInAnim(&card))
{
return;
}
if (this->isCardFaceUp(&card))
{
++cardFaceUpCounter;
faceUpCards[cardFaceUpCounter - 1] = &card;
}
}
I've just finished studying Beautiful C++ by Kate Gregory on Pluralsight.
She argues that we should avoid writing loops, and should use the STL algorithm header as much as possible.
I find her argument and approach very compelling, thus I have tried to refactor my latest pet project to reflect her teachings.
The example above is where I just can't see how I can use STL algorithms to both communicate the intent better and keep the performance - single loop instead of two or three loops, albeit hidden in the algorithm calls.
The second question would be if single loop efficiency can't be achieved using STL algorithms would you still prefer that approach for readability sake.

Here is an example of what I was thinking.
int cardFaceUpCounter = 0;
std::vector<Card*> faceUpCards(2);
if (std::any_of(boardMatrix.begin(), boardMatrix.end(), [&](auto& card) {
if (isCardFaceUp(&card))
faceUpCards[cardFaceUpCounter++] = &card;
return isCardInAnim(&card);
})) return;

With range-v3, it would be something like:
std::vector<Card*> faceUpCards = this->boardMatrix
| ranges::view::take_while([this](const auto& card){ return !isCardInAnim(&card);})
| ranges::view::filter([this](const auto& card){ return isCardFaceUp(&card); })
| ranges::view::transform([](auto& e){ return &e; });
With only STL, I would do something like:
auto it = std::find_if(boardMatrix.begin(), boardMatrix.end(),
[this](const auto& card){ return isCardInAnim(&card);});
std::vector<Card*> faceUpCards(std::distance(boardMatrix.begin(), it), nullptr);
std::transform(boardMatrix.begin(), it,
faceUpCards.begin(),
[](auto& card){ return &card;});
faceUpCards.erase(std::remove_if(faceUpCards.begin(), faceUpCards.end(),
[this](const auto& card){ return !isCardFaceUp(&card); }),
faceUpCards.end());

Related

How does std::remove perform compared to std::find for std::vector?

Background
I recently refactored some code that placed actors in a world. One actor per room.
My original implementation used a closed set std::vector to keep track of indexes of rooms that were no longer available:
void RoomsAndCorridorsMapGenerator::PlaceActors() noexcept {
const int room_count = static_cast<int>(rooms.size());
auto closed_set = std::vector<std::size_t>{};
closed_set.reserve(room_count);
for(auto* actor : _map->_actors) {
const auto room_idx = [&]() {
auto idx = static_cast<std::size_t>(MathUtils::GetRandomIntLessThan(room_count));
while(std::find(std::begin(closed_set), std::end(closed_set), idx) != std::end(closed_set)) {
idx = static_cast<std::size_t>(MathUtils::GetRandomIntLessThan(room_count));
}
closed_set.push_back(idx);
return idx;
}(); //IIIL
actor->SetPosition(IntVector2{rooms[room_idx].CalcCenter()});
}
}
That is:
Generate a random index.
Search the vector for the index generated.
If found, go to step 1.
Add the newly generated room index to the closed set.
For each actor, continue from step 1.
My refactored implementation used an open set:
void RoomsAndCorridorsMapGenerator::PlaceActors() noexcept {
auto open_set = std::vector<std::size_t>{};
open_set.resize(rooms.size());
std::iota(std::begin(open_set), std::end(open_set), std::size_t{0u});
for(auto* actor : _map->_actors) {
const auto room_idx = [&]() {
auto idx = open_set[static_cast<std::size_t>(MathUtils::GetRandomIntLessThan(static_cast<int>(open_set.size())))];
open_set.erase(std::remove(std::begin(open_set), std::end(open_set), idx), std::end(open_set));
return idx;
}(); //IIIL
actor->SetPosition(IntVector2{rooms[room_idx].CalcCenter()});
}
}
i.e.:
Pre-generate the list of available indexes, based on the number of rooms.
Pick a random value from the list.
Remove that value from the list.
For each actor, continue from step 2.
This simplified the code and the algorithm, but I'm not sure of the complexity.
Question
How does std::find to a vector compare to std::remove both in time and complexity?
I'm not sure if the repeated appending/erase-remove idiom also has an effect.

How to remove elements from a vector that uses the vector size in the for loop

I have a game where I shoot bullets at an object and I delete the object that gets hit by the bullet and bullet that are off screen as well.
For example:
std::vector<object> object_list;
for(size_t i = 0; i < object_list.size(); i++)
{
if(object_list[i].hit())
{
object_list.erase(object_list.begin() + i);
}
else
object_list[i].draw();
}
The problem with this is that when i remove an object, the size of the vector decreases so when it check the conditions, it fails and i get an error such as " vector subscript out of range." I could just choose not to render the asteroid by rendering those that haven't been hit, but the problem with that is that the no. of objects increases when hit(splits up) so eventually the program is going to get slower. I've used a similar concept for the off screen bullets but I can't find a way around it. I'm looking for a solution to this or better way of removing elements.
Both object and bullet are classes.
You should split for loop in 2 parts:
remove all "hit" elements:
object_list.erase(std::remove_if(object_list.begin(),
object_list.end(), [](auto&& item) { return item.hit(); }),
object_list.end());
draw remaining:
std::for_each(object_list.begin(), object_list.end(), [](auto&& item) { item.draw(); });
It's safer and more readable.
Same idea as the other answers but this code is a little easier with iterators
for (auto i = object_list.begin(); i != object_list.end(); )
{
if (i->hit())
{
i = object_list.erase(i);
}
else
{
i->draw();
++i;
}
}
vector::erase returns an iterator to the next element, which you can use to continue the loop.
Functional approach using the range-v3 library (C++20)
[...] I'm looking for a solution to this or better way of removing elements.
Using the ranges::actions::remove_if action from the range-v3 library, you can use a functional programming style approach to mutate the object_list container in-place:
object_list |= ranges::actions::remove_if(
[](const auto& obj) { return obj.hit(); });
followed by subsequent ranges:for_each invocation to draw the object:
ranges::for_each(object_list, [](const auto& obj){ obj.draw(); });
DEMO.
You could do something like this:
for (size_t i = 0; i < object_list.size(); )
{
if (object_list[i].hit())
object_list.erase(object_list.begin() + i)
else
{
object_list[i].draw()
++i;
}
}
Let us say you are at i=5 and that object has been hit, after deleting that element, the obj at i=6 is shifted to i=5, and you haven't checked it, so just add i--; after your erase statement.
Another way to do it would be -
for(size_t i = 0; i < object_list.size();)
{
if(object_list[i].hit())
{
object_list.erase(object_list.begin() + i);
}
else
{
object_list[i].draw();
i++;
}
}
Also, it could possibly be faster to just remove the object from the vector where you execute the code that marks the object as hit, that way you just need to draw all the objects which are left out in the list. Some more background into how you are doing all this would be helpful to decide something specific which would be better :)
The shown code does not fail or give a vector subscript out of range - it just does not consider every object, as it skips over the element after the removed one.
For very short and concise solutions employing concepts from C++11 and later, see the answer by Equod or the one by dfri
For better understanding the issue, and/or if you have to stick to for loops with indices, you basically have two options:
Iterate over the vector in reverse direction (i.e. start at the last element), then items after the current one being shifted is not a problem;
for (int i=object_list.size()-1; i>=0; --i)
{
if (object_list[i].hit())
{
object_list.erase(object_list.begin() + i)
}
else
{
object_list[i].draw()
}
}
Or, if the order is important (as I could imagine with items to draw), and you have to iterate from front to back, then only increase the counter i if you have not erased the current element:
for (int i=0; i<object_list.size(); /* No increase here... */ )
{
if (object_list[i].hit())
{
object_list.erase(object_list.begin() + i);
}
else
{
object_list[i].draw();
++i; // ...just here if we didn't remove the element
}
}
I suspect that std::vector is not the container you want (but, of course, I don't know the entire code). Each call to erase implies reallocation of the right-part of the vector (and then copies of you objects), it could be very costly. And your actual problem is the symptom of a design problem.
From what I see, std::list is probably better:
std::list<object> objects;
// ...
for(std::list<object>::iterator it = objects.begin(); it != objects.end();)
{
if(it->hit())
objects.erase(it++); // No object copied
else
{
it->draw();
++it;
}
}

How do I properly pass iterator by reference?

I have a game where I check collision between bullets and enemies which I store as 2 vector containers. People say if you're gonna erase an element in the for loop you better use iterators and so I did. But I have a problem now with passing the iterator to a function. The thing is I don't necessarily need to erase the element so it has to be a bit more complex.
This is the way I check collision. "CircularCollision" works fine, no mistakes there.
void ResolveColision(Weapon &weap, Map &map)
{
std::vector<Bullet> bullets = weap.GetBullets();
if (!bullets.empty())
{
for (std::vector<Bullet>::iterator i = bullets.begin(); i != bullets.end(); ++i)
{
std::vector<Enemy> enemies = map.GetEnemies();
if (!enemies.empty())
{
for (std::vector<Enemy>::iterator j = enemies.begin(); j != enemies.end(); ++j)
{
if (CircularCollision((*i), (*j)))
{
weap.DeleteByIndex(i);
map.TakeDamageByIndex(j, weap.GetDamage());
std::cout << "HIT!\n";
}
}
}
}
}
}
Here's the method which is supposed to decrease the health of an enemy:
void Map::TakeDamageByIndex(std::vector<Enemy>::iterator &itr, int damage)
{
(*itr).SetHealth((*itr).GetHealth() - damage);
}
Here's the method which deletes the bullet:
void Weapon::DeleteByIndex(std::vector<Bullet>::iterator &itr)
{
destroySprite((*itr).GetSprite());
bullets.erase(itr);
}
I'm sure it looks horrible and it shouldn't work but I have no idea how to do it properly. Please help!
Also, both methods work properly when the for loops operate with indexes (e.g. bullets[i]), in that case the problem is with "Vector subscript out of range" error.
In DeleteByIndex(), change this:
bullets.erase(itr);
To this:
itr = bullets.erase(itr);
std::vector::erase() returns an iterator to the next remaining element after the element that was erased. That next element is where your outer loop needs to continue from on its next iteration.
As such, you need to change your outer loop from a for to a while instead, or else you will skip elements (in fact, your original code suffers from that problem when you were still using indexes):
void ResolveColision(Weapon &weap, Map &map)
{
std::vector<Bullet> bullets = weap.GetBullets();
std::vector<Bullet>::iterator bullerItr = bullets.begin();
while (bullerItr != bullets.end())
{
std::vector<Enemy> enemies = map.GetEnemies();
bool wasAnyHit = false;
for (std::vector<Enemy>::iterator enemyItr = enemies.begin(); enemyItr != enemies.end(); ++enemyItr)
{
if (CircularCollision(*bulletItr, *enemyItr))
{
wasAnyHit = true;
weap.DeleteByIndex(bulletItr);
map.TakeDamageByIndex(enemyItr, weap.GetDamage());
std::cout << "HIT!\n";
break;
}
}
if (!wasAnyHit)
++bulletItr;
}
}
That being said, I would suggest replacing the inner loop with std::find_if() instead. And renaming DeleteByIndex() and TakeDamageByIndex() since they don't take an index anymore. In fact, I would not pass an iterator to TakeDamage...() at all, pass the actual Enemy object instead. Or better, move TakeDamage() into Enemy itself.
Try something more like this:
void ResolveColision(Weapon &weap, Map &map)
{
auto bullets = weap.GetBullets();
auto bulletItr = bullets.begin();
while (bulletItr != bullets.end())
{
auto enemies = map.GetEnemies();
auto &bullet = *bulletItr;
auto enemyHit = std::find_if(enemies.begin(), enemies.end(),
[&](Enemy &enemy){ return CircularCollision(bullet, enemy); }
);
if (enemyHit != enemies.end())
{
weap.DeleteBulletByIterator(bulletItr);
enemyHit->TakeDamage(weap.GetDamage());
std::cout << "HIT!\n";
}
else
++bulletItr;
}
}
void Enemy::TakeDamage(int damage)
{
SetHealth(GetHealth() - damage);
}
void Weapon::DeleteBulletByIterator(std::vector<Bullet>::iterator &itr)
{
destroySprite(itr->GetSprite());
itr = bullets.erase(itr);
}
A few other comments in addition to Remy Lebeau’s answer.
It’s as efficient to pass a STL iterator by value as by reference, so the only reason you would need to pass one by reference is: when you intend to change the index and you want that change to be visible in the caller’s scope. (For example, a UTF-8 parser needs to consume anywhere from one to four bytes.) Since this code doesn’t need to do that, you’re better off just passing the iterator by value.
In general, if you aren’t modifying the variable you pass by reference, you should pass by const reference instead. In the case of Enemy::TakeDamage(), the only thing you do with the iterator is dereference it, so you might as well just pass in an Enemy& and call it with *i as the parameter.
The algorithm is not very efficient: if you delete a lot of items near the start of the list, you would need to move all remaining items of the array multiple times. This runs in O(N²) time. A std::list, although it has a high overhead compared to std::vector, can delete elements in constant time, and might be more efficient if you have a lot of insertions and deletions that are not at the end. You might also consider moving only the objects that survive to a new list and then destroying the old one. At least this way, you only need to copy once, and your pass runs in O(N) time.
If your containers store smart pointers to the objects, you only have to move the pointers to a new location, not the entire object. This will not make up for the overhead of lots of heap allocations if your objects are small, but could save you a lot of bandwidth if they are large. The objects will still be automatically deleted when the last reference to them is cleared.
You could do something like this:
void delByIndex(vector<int>::iterator &i, vector<int>& a)
{
a.erase(i);
}
int main()
{
vector<int> a {1,5,6,2,7,8,3};
vector<int> b {1,2,3,1};
for(auto i=a.begin();i!=a.end();)
{
bool flag = false;
for(auto j=b.begin();j!=b.end();j++)
{
if(*i==*j)
{
flag = true;
}
}
if(flag)
{
delByIndex(i, a);
}
else
i++;
}
for(auto i:a)
cout << i << " ";
return 0;
}
Be careful when using erase as it will change the size of the vector and also invalidates the vector iterator.

fast way to compare two vector containing strings

I have a vector of strings I that pass to my function and I need to compare it with some pre-defined values. What is the fastest way to do this?
The following code snippet shows what I need to do (This is how I am doing it, but what is the fastest way of doing this):
bool compare(vector<string> input1,vector<string> input2)
{
if(input1.size() != input2.size()
{
return false;
}
for(int i=0;i<input1.siz();i++)
{
if(input1[i] != input2[i])
{
return false;
}
}
return true;
}
int compare(vector<string> inputData)
{
if (compare(inputData,{"Apple","Orange","three"}))
{
return 129;
}
if (compare(inputData,{"A","B","CCC"}))
{
return 189;
}
if (compare(inputData,{"s","O","quick"}))
{
return 126;
}
if (compare(inputData,{"Apple","O123","three","four","five","six"}))
{
return 876;
}
if (compare(inputData,{"Apple","iuyt","asde","qwe","asdr"}))
{
return 234;
}
return 0;
}
Edit1
Can I compare two vector like this:
if(inputData=={"Apple","Orange","three"})
{
return 129;
}
You are asking what is the fastest way to do this, and you are indicating that you are comparing against a set of fixed and known strings. I would argue that you would probably have to implement it as a kind of state machine. Not that this is very beautiful...
if (inputData.size() != 3) return 0;
if (inputData[0].size() == 0) return 0;
const char inputData_0_0 = inputData[0][0];
if (inputData_0_0 == 'A') {
// possibly "Apple" or "A"
...
} else if (inputData_0_0 == 's') {
// possibly "s"
...
} else {
return 0;
}
The weakness of your approach is its linearity. You want a binary search for teh speedz.
By utilising the sortedness of a map, the binaryness of finding in one, and the fact that equivalence between vectors is already defined for you (no need for that first compare function!), you can do this quite easily:
std::map<std::vector<std::string>, int> lookup{
{{"Apple","Orange","three"}, 129},
{{"A","B","CCC"}, 189},
// ...
};
int compare(const std::vector<std::string>& inputData)
{
auto it = lookup.find(inputData);
if (it != lookup.end())
return it->second;
else
return 0;
}
Note also the reference passing for extra teh speedz.
(I haven't tested this for exact syntax-correctness, but you get the idea.)
However! As always, we need to be context-aware in our designs. This sort of approach is more useful at larger scale. At the moment you only have a few options, so the addition of some dynamic allocation and sorting and all that jazz may actually slow things down. Ultimately, you will want to take my solution, and your solution, and measure the results for typical inputs and whatnot.
Once you've done that, if you still need more speed for some reason, consider looking at ways to reduce the dynamic allocations inherent in both the vectors and the strings themselves.
To answer your follow-up question: almost; you do need to specify the type:
// new code is here
// ||||||||||||||||||||||||
if (inputData == std::vector<std::string>{"Apple","Orange","three"})
{
return 129;
}
As explored above, though, let std::map::find do this for you instead. It's better at it.
One key to efficiency is eliminating needless allocation.
Thus, it becomes:
bool compare(
std::vector<std::string> const& a,
std::initializer_list<const char*> b
) noexcept {
return std::equal(begin(a), end(a), begin(b), end(b));
}
Alternatively, make them static const, and accept the slight overhead.
As an aside, using C++17 std::string_view (look at boost), C++20 std::span (look for the Guideline support library (GSL)) also allows a nicer alternative:
bool compare(std::span<std::string> a, std::span<std::string_view> b) noexcept {
return a == b;
}
The other is minimizing the number of comparisons. You can either use hashing, binary search, or manual ordering of comparisons.
Unfortunately, transparent comparators are a C++14 thing, so you cannot use std::map.
If you want a fast way to do it where the vectors to compare to are not known in advance, but are reused so can have a little initial run-time overhead, you can build a tree structure similar to the compile time version Dirk Herrmann has. This will run in O(n) by just iterating over the input and following a tree.
In the simplest case, you might build a tree for each letter/element. A partial implementation could be:
typedef std::vector<std::string> Vector;
typedef Vector::const_iterator Iterator;
typedef std::string::const_iterator StrIterator;
struct Node
{
std::unique_ptr<Node> children[256];
std::unique_ptr<Node> new_str_child;
int result;
bool is_result;
};
Node root;
int compare(Iterator vec_it, Iterator vec_end, StrIterator str_it, StrIterator str_end, const Node *node);
int compare(const Vector &input)
{
return compare(input.begin(), input.end(), input.front().begin(), input.front().end(), &root);
}
int compare(Iterator vec_it, Iterator vec_end, StrIterator str_it, StrIterator str_end, const Node *node)
{
if (str_it != str_end)
{
// Check next character
auto next_child = node->children[(unsigned char)*str_it].get();
if (next_child)
return compare(vec_it, vec_end, str_it + 1, str_end, next_child);
else return -1; // No string matched
}
// At end of input string
++vec_it;
if (vec_it != vec_end)
{
auto next_child = node->new_str_child.get();
if (next_child)
return compare(vec_it, vec_end, vec_it->begin(), vec_it->end(), next_child);
else return -1; // Have another string, but not in tree
}
// At end of input vector
if (node->is_result)
return node->result; // Got a match
else return -1; // Run out of input, but all possible matches were longer
}
Which can also be done without recursion. For use cases like yours you will find most nodes only have a single success value, so you can collapse those into prefix substrings, to use the OP example:
"A"
|-"pple" - new vector - "O" - "range" - new vector - "three" - ret 129
| |- "i" - "uyt" - new vector - "asde" ... - ret 234
| |- "0" - "123" - new vector - "three" ... - ret 876
|- new vector "B" - new vector - "CCC" - ret 189
"s" - new vector "O" - new vector "quick" - ret 126
you could make use of std::equal function like below :
bool compare(vector<string> input1,vector<string> input2)
{
if(input1.size() != input2.size()
{
return false;
}
return std::equal(input1.begin(), input2.end(), input2.begin())
}
Can I compare two vector like this
The answer is No, you need compare a vector with another vector, like this:
vector<string>data = {"ab", "cd", "ef"};
if(data == vector<string>{"ab", "cd", "efg"})
cout << "Equal" << endl;
else
cout << "Not Equal" << endl;
What is the fastest way to do this?
I'm not an expert of asymptotic analysis but:
Using the relational operator equality (==) you have a shortcut to compare two vectors, first validating the size and, second, each element on them. This way provide a linear execution (T(n), where n is the size of vector) which compare each item of the vector, but each string must be compared and, generally, it is another linear comparison (T(m), where m is the size of the string).
Suppose that each string has de same size (m) and you have a vector of size n, each comparison could have a behavior of T(nm).
So:
if you want a shortcut to compare two vector you can use the
relational operator equality.
If you want an program which perform a fast comparison you should look for some algorithm for compare strings.

Removing an object from vector while iterating through it

I'm new to C++ so I'm having trouble figuring out how to best remove an object from a vector while still iterating through it.
Basically, I need to iterate through two vectors. For every item, if the ID's match, I can remove them.
//For every person, check to see if the available bags match:
for(std::vector<Person>::iterator pit = waitingPeopleVector.begin(); pit != waitingPeopleVector.end(); ++pit) {
for(std::vector<Bag>::iterator bit = waitingBagsVector.begin(); bit != waitingBagsVector.end(); ++bit) {
int pId = pit->getId();
int bId = bit->getId();
if(pId == bId){
//a match occurs, remove the bag and person
}
}
}
Working with iterators is a bit confusing, I know I can use the .erase() function on my vectors, but I can't really pass pit or bit. Any help appreciated. Thanks
From the standard:
The iterator returned from a.erase(q) points to the element
immediately following q prior to the element being erased. If no such
element exists, a.end() is returned.
I would use it in something like using the erase method:
std::vector<Person>::iterator pit = waitingPeopleVector.begin();
std::vector<Bag>::iterator bit = waitingBagsVector.begin();
while (pit != waitingPeopleVector.end())
{
bool didit;
while (bit != waitingBagsVector.end())
{
didit = false;
if (pit->getId() == bit->getId() && !didit)
{
bit = waitingBagsVector.erase(bit);
pit = waitingPeopleVector.erase(pit);
didit = true;
}
else
{
++bit;
}
}
if (didit)
continue;
else
++pit;
}
Using the erase-remove idiom will achieve this objective, the below offers an (untested) way using lambdas and <algorithm> functions to remove elements from wPL which have the same ID as wBL. It shouldn't be too much effort to extend this to both lists. Note, we have used std::list instead of std::vector for faster removal.
std::list<Person> wPL;
std::list<Bag> wBL;
//...
wPL.erase(std::remove_if(wPL.begin(), wPL.end(),
[&wBL](auto x) { return std::find_if(wBL.begin(), wBL.end(), [](auto y)
{ return x.getId() == y.getId();); }), wPL.end() };