std::multiset define comparator for insertion and comparison - c++

I'm using a std::multiset of pointers to objects to implement Z-ordering in my game, so I don't need to sort the structure on each insertion. I use a comparator for insertion by the object's depth:
struct rendererComparator
{
bool operator ()(const Renderable* r1, const Renderable* r2) const
{
return r1->depth < r2->depth;
}
};
std::multiset<Renderable*, rendererComparator> m_Renderables;
However when it comes to erasing an element in the multiset, the call to erase removes all elements which have the same depth which is undesirable. I tried the suggestions in this question: In std::multiset is there a function or algorithm to erase just one sample (unicate or duplicate) if an element is found but
auto iterator = m_Renderables.find(renderable);
if (iterator != m_Renderables.end())
{
m_Renderables.erase(renderable);
}
Still erases all the elements with the same depth because of the comparator.
Is it possible to define 2 comparators for std::multiset without boost? (How can I set two kind of comparator (one for insert, one for find) on this multiset?) One for insertion and one for comparison?
Thanks
Edit: Jignatious pointed out that I wasn't erasing the iterator (typo by me). I solved it by using std::find_if
auto iterator = std::find_if(m_Renderables.begin(), m_Renderables.end(), [renderable](const Renderable* r1) { return r1 == renderable; });
if (iterator != m_Renderables.end())
{
m_Renderables.erase(iterator);
}

The problem is on this line:
m_Renderables.erase(renderable);
which erases all elements with the same value.
You need to erase with the iterator from the find() function call instead. That will erase the single element that the iterator points to:
m_Renderables.erase(iterator);
Note that std::multiset::find() returns an iterator pointing to the lower bound (or first) of the elements which is searched in the multiset if it exists, otherwise the one past the end element iterator.

Instead of multiset, you can use std::set with comparer like this:
struct Element
{
int value;
Element(int v)
{
value = v;
}
bool operator() (Element* const& left, Element* const& right) const
{
if (left->value == right->value)
return (left < right);
return left->value < right->value;
}
};
It will store multiple values like multimap, but without 'erase all' on erase, without replace same values on insert and with proper find by reference.
std::set<Element*, Element> set;
set.insert(new Element(10));
auto last = new Element(10);
set.insert(last); // 10 10 like in multiset
set.erase(last); // will delete proper references

Related

Binary search on vector objects of an element greater than an attribute

I have a vector which contains lot of elements of my class X .
I need to find the first occurrence of an element in this vector say S such that S.attrribute1 > someVariable. someVariable will not be fixed . How can I do binary_search for this ? (NOT c++11/c++14) . I can write std::binary_search with search function of greater (which ideally means check of equality) but that would be wrong ? Whats the right strategy for fast searching ?
A binary search can only be done if the vector is in sorted order according to the binary search's predicate, by definition.
So, unless all elements in your vector for which "S.attribute1 > someVariable" are located after all elements that are not, this is going to be a non-starter, right out of the gate.
If all elements in your vector are sorted in some other way, that "some other way" is the only binary search that can be implemented.
Assuming that they are, you must be using a comparator, of some sort, that specifies strict weak ordering on the attribute, in order to come up with your sorted vector in the first place:
class comparator {
public:
bool operator()(const your_class &a, const your_class &b) const
{
return a.attribute1 < b.attribute1;
}
};
The trick is that if you want to search using the attribute value alone, you need to use a comparator that can be used with std::binary_search which is defined as follows:
template< class ForwardIt, class T, class Compare >
bool binary_search( ForwardIt first, ForwardIt last,
const T& value, Compare comp );
For std::binary_search to succeed, the range [first, last) must be
at least partially ordered, i.e. it must satisfy all of the following
requirements:
for all elements, if element < value or comp(element, value) is true
then !(value < element) or !comp(value, element) is also true
So, the only requirement is that comp(value, element) and comp(element, value) needs to work. You can pass the attribute value for T, rather than the entire element in the vector to search for, as long as your comparator can deal with it:
class search_comparator {
public:
bool operator()(const your_class &a, const attribute_type &b) const
{
return a.attribute1 < b;
}
bool operator()(const attribute_type &a, const your_class &b) const
{
return a < b.attribute1;
}
};
Now, you should be able to use search_comparator instead of comparator, and do a binary search by the attribute value.
And, all bets are off, as I said, if the vector is not sorted by the given attribute. In that case, you'll need to use std::sort it explicitly, first, or come up with some custom container that keeps track of the vector elements, in the right order, separately and in addition to the main vector that holds them. Using pointers, perhaps, in which case you should be able to execute a binary search on the pointers themselves, using a similar search comparator, that looks at the pointers, instead.
For std::binary_search to succeed, the range need to be sorted.std::binary_search, std::lower_bound works on sorted containers. So every time you add a new element into your vector you need to keep it sorted.
For this purpose you can use std::lower_bound in your insertion:
class X;
class XCompare
{
public:
bool operator()(const X& first, const X& second) const
{
// your sorting logic
}
};
X value(...);
auto where = std::lower_bound(std::begin(vector), std::end(vector), value, XCompare());
vector.insert(where, value);
And again you can use std::lower_bound to search in your vector:
auto where = std::lower_bound(std::begin(vector), std::end(vector), searching_value, XCompare());
Don't forget to check if std::lower_bound was successful:
bool successed = where != std::end(vector) && !(XCompare()(value, *where));
Or directly use std::binary_search if you only want to know that element is in vector.

How do I remove an element from a vector based on a condition?

I have a vector which I use for an observer pattern to register the name and pointer: a registration list.
I want to unregister an observers from the vector pair.
I am not sure how to proceed, I tried this but does not compile at all.
vector < pair<string , Observer* > > destination;
void subject::unregisterObservers(LogObserver* obsIfP)
{
vector <pair<string, LogObserverIf*> > ::iterator it;
for(it = observers.begin(); it != observers.end(); it++)
{
if((*it).second == obsIfP)
{
remove(observers.begin(), observers.end(), (*it).first);
break;
}
}
}
How do I remove elements from the vector based on one of the values inside a pair element?
You should use vector::erase() instead.
for(it = observers.begin(); it != observers.end(); it++)
{
if(it->second == obsIfP)
{
observers.erase(it);
break;
}
}
There's a few issues with your current code.
std::remove will find and move elements equal to the given value to the end of the container. It then returns the iterator pointing to the end of the non-removed range in the vector. To have them completely removed would require vector.erase with the iterator returned from the remove algorithm.
The erase-remove idiom:
v.erase( remove( begin(v), end(v), value ), end(v) )
Note, your code gives a string as value and will not compile, since elements in the vector are pair< string, Observer* > and the algorithm can't compare between the two.
Erasing while iterating over the same range is dangerous, since you may invalidate the iterators of the first loop.
Using algorithms with predicates:
Depending on the size of the vector, it may just be simpler (and even faster) to generate a new vector.
typedef pair<string, Observer*> RegPair;
vector<RegPair> new_observers;
new_observers.reserve( observers.size() ); // To avoid resizing during copy.
remove_copy_if( begin(observers), end(obervers), std::back_inserter(new_observers),
[obsIfP]( const RegPair& p ) -> bool
{ return p.second == obsIfP; } );
observers = std::move(new_observers);
// --- OR THIS
observers.erase( remove_if( begin(observers), end(observers),
[obsIfP]( const RegPair& p ) -> bool
{ return p.second == obsIfP; } ),
end(observers) );
Removing an element in the middle of a vector will cause all the following elements to be moved back one index, which is essentially just a copy anyway. If more than one element has the observer pointer, your initial code would have to move these elements more than once, while this suggestion always has a worst case of O(N), where the elementary operation is a copy of the pair element. If better performance than O(N) is required, you'll have to arrange your observers with std::map< string, Observer* > or perhaps boost::bimap which lets you use both pair values as keys.
The difference between remove_if and copy_remove_if would be the preserving of order. remove_if may swap elements and place them elsewhere in order to get the removed element to the end-range. The copy_remove_if will simply not copy it over to the new container, so order is preserved.
If you're not using c++11, the lambda wont work and you'll have to hand code the loop yourself.

What is the std::vector::iterator's index in the vector?

I have an std::vector<Bullet> bullets and in the for-loop below I want to remove a bullet from the vector if it's not alive anymore.
My plan is to remove the element with pop_back(). If there are more than one element in the vector I want to first swap the element that is to be removed with the last element in the vector, and then call pop_back().
for (std::vector<Bullet>::iterator b = bullets.begin(); b != bullets.end(); ++b) {
if(!b->isAlive()) {
if (bullets.size() > 1) {
std::iter_swap(bullets + ..., bullets.end());
}
bullets.pop_back();
}
}
The problem is the first parameter in iter_swap. I looked up http://www.cplusplus.com/reference/algorithm/iter_swap/ and the syntax for the first parameter is the vector + the position of the element.
How do I find out b's index in the vector?
If the condition governing whether an element is to be removed or not is :
object->isAlive()
Then you should use an STL way to do the removal, namely the erase-remove idiom :
bullets.erase(std::remove_if(bullets.begin(), bullets.end(),
[](Bullet const& b) {
return !b.isAlive();
}), bullets.end());
Now, to answer your particular question an iterator's it index in a vector v can be obtained like so :
auto indx = std::distance(v.begin(), it);
There's an easier way to filter a std::vector.
#include <algorithm>
auto part = std::remove_if(
bullets_.begin(),
bullets_.end(),
[](const Bullet &bullet) { return !bullet.isAlive(); });
bullets_.erase(part, bullets_.end());
This will partition the vector into the alive and dead bullets, and then you delete the segment with the dead bullets.
The std::remove_if() function is like partition() but only the order of the first partition is preserved.

const_pointer_cast of a std::set iterator

I've got a bit of a conundrum here. I am writing a mesh (grid) generator. In doing so, I need to obtain a list of lower dimensional elements from higher dimensional elements (i.e. a "triangle" element is composed of 3 "line" elements). I need the list of unique line elements and I need the parent element to store a pointer to each of its unique child elements.
To this end, I create a std::set of smart pointers to the child elements and try to insert each child element to the list. Every time I try to insert an element I ask for a pair containing an iterator to the pre-existing element and a boolean specifying whether or not the element has been inserted.
The problem is that std::set (and map) return constant iterators to the pre-existing element. However, I need to give the parent element of the element that I failed to insert a non-constant pointer to the pre-existing element. So I use const_pointer_cast<> to cast the constant iterator to a non-constant one.
Is there a better way of going about this? Doing a const_pointer_cast() is probably not ideal. Is there something that I should be using for this application instead of std::set?
Edit: here's the function:
template<class T1, class T2>
int getUniqueSubelements(std::set<std::shared_ptr<T1>, elGreater>& elements,
std::set<std::shared_ptr<T2>, elGreater>& childels)
{
typedef std::shared_ptr<T2> child_ptr;
std::pair<typename std::set<child_ptr, elGreater>::iterator, bool> ret;
for (auto parit = elements.begin(); parit != elements.end(); ++parit)
{
(*parit)->generateChildren();
const std::vector<child_ptr>& children = (*parit)->getChildren();
for (int i = 0; i < children.size(); i++)
{
ret = childels.insert(children[i]);
if (ret.second == false)
{
child_ptr temp = std::const_pointer_cast<T2>(*ret.first);
bool orient = elOrientation(children[i], temp);
children[i]->swapInParent(temp, orient);
}
}
}
return 1;
}
No cast at all is needed here.
It's true that std::set<K, Comp>::iterator is the same as std::set<K, Comp>::const_iterator, and that iterator::operator* returns a const K&.
But in your case, this means that the type of *ret.first is const std::shared_ptr<T2>&. This is the analogue of (T2* const), not (const T2*): you can't modify the smart pointer, but you can modify the actual stored object.
So assuming elOrientation is something like
template <typename T>
bool elOrientation(std::shared_ptr<T>, std::shared_ptr<T>);
you can just call elOrientation(children[i], *ret.first).

What's wrong with my vector<T>::erase here?

I have two vector<T> in my program, called active and non_active respectively. This refers to the objects it contains, as to whether they are in use or not.
I have some code that loops the active vector and checks for any objects that might have gone non active. I add these to a temp_list inside the loop.
Then after the loop, I take my temp_list and do non_active.insert of all elements in the temp_list.
After that, I do call erase on my active vector and pass it the temp_list to erase.
For some reason, however, the erase crashes.
This is the code:
non_active.insert(non_active.begin(), temp_list.begin(), temp_list.end());
active.erase(temp_list.begin(), temp_list.end());
I get this assertion:
Expression:("_Pvector == NULL || (((_Myvec*)_Pvector)->_Myfirst <= _Ptr && _Ptr <= ((_Myvect*)_Pvector)->_Mylast)",0)
I've looked online and seen that there is a erase-remove idiom, however not sure how I'd apply that to a removing a range of elements from a vector<T>
I'm not using C++11.
erase expects a range of iterators passed to it that lie within the current vector. You cannot pass iterators obtained from a different vector to erase.
Here is a possible, but inefficient, C++11 solution supported by lambdas:
active.erase(std::remove_if(active.begin(), active.end(), [](const T& x)
{
return std::find(temp_list.begin(), temp_list.end(), x) != temp_list.end();
}), active.end());
And here is the equivalent C++03 solution without the lambda:
template<typename Container>
class element_of
{
Container& container;
element_of(Container& container) : container(container) {}
public:
template<typename T>
bool operator()(const T& x) const
{
return std::find(container.begin(), container.end(), x)
!= container.end();
}
};
// ...
active.erase(std::remove_if(active.begin(), active.end(),
element_of<std::vector<T> >(temp_list)),
active.end());
If you replace temp_list with a std::set and the std::find_if with a find member function call on the set, the performance should be acceptable.
The erase method is intended to accept iterators to the same container object. You're trying to pass in iterators to temp_list to use to erase elements from active which is not allowed for good reasons, as a Sequence's range erase method is intended to specify a range in that Sequence to remove. It's important that the iterators are in that sequence because otherwise we're specifying a range of values to erase rather than a range within the same container which is a much more costly operation.
The type of logic you're trying to perform suggests to me that a set or list might be better suited for the purpose. That is, you're trying to erase various elements from the middle of a container that match a certain condition and transfer them to another container, and you could eliminate the need for temp_list this way.
With list, for example, it could be as easy as this:
for (ActiveList::iterator it = active.begin(); it != active.end();)
{
if (it->no_longer_active())
{
inactive.push_back(*it);
it = active.erase(it);
}
else
++it;
}
However, sometimes vector can outperform these solutions, and maybe you have need for vector for other reasons (like ensuring contiguous memory). In that case, std::remove_if is your best bet.
Example:
bool not_active(const YourObjectType& obj);
active_list.erase(
remove_if(active_list.begin(), active_list.end(), not_active),
active_list.end());
More info on this can be found under the topic, 'erase-remove idiom' and you may need predicate function objects depending on what external states are required to determine if an object is no longer active.
You can actually make the erase/remove idiom usable for your case. You just need to move the value over to the other container before std::remove_if possibly shuffles it around: in the predicate.
template<class OutIt, class Pred>
struct copy_if_predicate{
copy_if_predicate(OutIt dest, Pred p)
: dest(dest), pred(p) {}
template<class T>
bool operator()(T const& v){
if(pred(v)){
*dest++ = v;
return true;
}
return false;
}
OutIt dest;
Pred pred;
};
template<class OutIt, class Pred>
copy_if_predicate<OutIt,Pred> copy_if_pred(OutIt dest, Pred pred){
return copy_if_predicate<OutIt,Pred>(dest,pred);
}
Live example on Ideone. (I directly used bools to make the code shorter, not bothering with output and the likes.)
The function std::vector::erase requires the iterators to be iterators into this vector, but you are passing iterators from temp_list. You cannot erase elements from a container that are in a completely different container.
active.erase(temp_list.begin(), temp_list.end());
You try to erase elements from one list, but you use iterators for second list. First list iterators aren't the same, like in second list.
I would like to suggest that this is an example of where std::list should be used. You can splice members from one list to another. Look at std::list::splice()for this.
Do you need random access? If not then you don't need a std::vector.
Note that with list, when you splice, your iterators, and references to the objects in the list remain valid.
If you don't mind making the implementation "intrusive", your objects can contain their own iterator value, so they know where they are. Then when they change state, they can automate their own "moving" from one list to the other, and you don't need to transverse the whole list for them. (If you want this sweep to happen later, you can get them to "register" themselves for later moving).
I will write an algorithm here now to run through one collection and if a condition exists, it will effect a std::remove_if but at the same time will copy the element into your "inserter".
//fwd iterator must be writable
template< typename FwdIterator, typename InputIterator, typename Pred >
FwdIterator copy_and_remove_if( FwdIterator inp, FwdIterator end, InputIterator outp, Pred pred )
{
for( FwdIterator test = inp; test != end; ++test )
{
if( pred(*test) ) // insert
{
*outp = *test;
++outp;
}
else // keep
{
if( test != inp )
{
*inp = *test;
}
++inp;
}
}
return inp;
}
This is a bit like std::remove_if but will copy the ones being removed into an alternative collection. You would invoke it like this (for a vector) where isInactive is a valid predicate that indicates it should be moved.
active.erase( copy_and_remove_if( active.begin(), active.end(), std::back_inserter(inactive), isInactive ), active.end() );
The iterators you pass to erase() should point into the vector itself; the assertion is telling you that they don't. This version of erase() is for erasing a range out of the vector.
You need to iterate over temp_list yourself and call active.erase() on the result of dereferencing the iterator at each step.