Removing duplicates in multigroup - c++

I'm trying to remove elements that have the same key and value in a multimap. This is my code for now. After deleting the element, I get exception.
multimap<string, CStudent> m_StudentMap;
void removeDuplicates() {
for (auto it1 = m_StudentMap.begin(); it1 != --m_StudentMap.end(); it1++) {
for (auto it2 = next(it1, 1); it2 != m_StudentMap.end(); it2++) {
if (it1->first == it2->first) {
if (it1->second == it2->second) {
m_StudentMap.erase(it2);
}
}
}
}
}

You were nearly right, but the trick with erasing elements in maps while iterating is to capture the new iterator returned by erase. I've also generalised the function so it can be used on an argument rather than being limited to m_StudentMap, and stopped the inner loop as soon as the keys diverge.
template <typename K, typename V>
void removeDuplicates(std::multimap<K, V>& mmap)
{
if (mmap.size() < 2) return;
for (auto it = mmap.begin(); it != prev(mmap.end()); ++it)
for (auto it2 = next(it); it2 != mmap.end() && it2->first == it->first; )
if (it->second == it2->second)
it2 = mmap.erase(it2);
else
++it2;
}
You can see it run / fork it etc. here.

Related

Creating a binary search for use in a templated std list

template<typename C> bool binary_search(list<C>& a, C val) {
typename list <C>::iterator it;
typename list <C>::iterator it2;
typename list <C>::iterator it3;
size_t elements = a.size();
for (it = a.begin(), it3 = a.end(); it != it3; ) {
//find middle element
elements = elements / 2;
for (it2 = it; it2 < elements; it2++) {
}
if (*it2 < val)
*it = *it2;
else if (*it2 > val)
*it3 = *it2;
else if (*it2 = val)
return true;
else
return false;
}
}
Hello! I am having trouble implementing this search function.
val in the parameters is the value we are searching for.
when I try to compile the code I get
error C2676: binary '<': 'std::_List_iterator<std::_List_val<std::_List_simple_types<_Ty>>>'
does not define this operator or a conversion to a type acceptable to the predefined operator
This error occurs at:
for (it2 = it; it2 < elements; it2++)
also is there a better way to find the middle element?
elements holds the index of the last element you want to check.
it2 is an iterator. operator< is not defined for those types. You can use it and elements to create an iterator to make comparisons possible.
size_t elements = a.size();
for (it = a.begin(), it3 = a.end(); it != it3; ) {
//find middle element
elements = elements / 2;
auto end = std::next(it, elements); // added iterator
for (it2 = it; it2 != end; it2++) { // use added iterator
}
if (*it2 < val)
*it = *it2;
else if (*it2 > val)
*it3 = *it2;
else if (*it2 = val) // note: this assigns val to *it2, probably a bug
return true;
else
return false;
}
Pretty sure this is not a good way to solve this lol, but this is my final version and works for my current assignment.
template<typename C> bool binary_search(list<C>& a, C val) {
typename list <C>::iterator it;
typename list <C>::iterator it2;
typename list <C>::iterator it3 = a.end();
size_t elements = a.size();
size_t i = 0;
for (it = a.begin(), it3--; it != it3; ) {
//find middle element
elements = elements / 2;
for (it2 = it, i = 0; i < elements; it2++, i++) {
}
if (*it2 < val)
it = it2;
else if (*it2 > val)
it3 = it2;
else if (*it2 = val)
return true;
else
return false;
}
}
Thanks to fas for pointing out what was wrong!
To move an iterator by a number of places you can use std::advance:
elements = elements / 2;
it2 = it;
std::advance(it2, elements);
Note however that performing a binary search on a std::list is unlikely to be efficient unless you have a very expensive comparison function. If you want random access you should use std::vector instead. std::vector's iterators also allow you to just add numbers to them without using std::advance:
elements = elements / 2;
it2 = it + elements;
I assume you are implementing binary search as an exercise but if not note that the standard library does have a binary search function it just has a non obvious name: std::upper_bound std::lower_bound

Removing one element of a vector while in a loop

for (Shape *i : shapes) {
for (Shape *j : shapes) {
if (i != j) {
if (check(i,j)){
shapes.erase(remove(shapes.begin(), shapes.end(), i), shapes.end());
this causes an error because it's going to carry on iterating even though i does not exist, my question is how do I cleanly do this? currently I get an error "vector iterator not incrementable"
Can i just exit the second loop and continue in the first one?
You cannot erase elements from a vector when you are iterating it by for range loop, as internally it uses iterators that would be invalidated. This should work:
auto end = shapes.end();
for( auto it = shapes.begin(); it != end; ++it ) {
end = shapes.erase( std::remove_if( std::next( it ), shapes.end(), [it]( Shape *s ) {
return check( *it, s );
}, shapes.end() );
}
Note this code is slightly more effective than yours but it assumes that check( s1, s2 ) == check( s2, s1 ), if you can change your check() function to do strict ordering comparison, rather than equivalence, then you would be able to use std::sort and std::unique which are even more effective.
You can't modify the positioning of your shapes elements while using ranged-based for loops. The range loop uses iterators internally, and erasing vector elements invalidates existing iterators.
Try something more like this instead:
auto iter = shapes.begin();
auto end = shapes.end();
while (iter != end) {
auto iter2 = shapes.begin();
bool erased = false;
while (iter2 != end) {
if ((iter != iter2) && check(*iter, *iter2)) {
iter = shapes.erase(iter);
end = shapes.end();
erased = true;
break;
}
++iter2;
}
if (!erased)
++iter;
}
Alternatively, maybe something more like this would also work:
shapes.erase(
std::remove_if(shapes.begin(), shapes.end(),
[shapes&](Shape *i) {
for (Shape *j : shapes) {
if ((i != j) && check(i, j)) {
return true;
}
}
return false;
}
),
shapes.end()
);
You cannot use a range-for loop in this case. Instead use a standard loop with iterators:
for (auto iter = shapes.begin(); iter != shapes.end(); iter++)

Vector Iterator erase two elements on condition

I'm currently trying to delete 2 elements from a vector if some condition is met. I can successfully remove a single element without the "vector iterator not dereferencable" error occuring, I know the problem is been caused by removing two elements at once which messes up with the Iterators but am unsure as to the correct way of removing more than one element at once.
vector<SomeObj*> objs;
vector<SomeObj*>::iterator it = objs.begin();
while (it != objs.end())
{
vector<SomeObj*>::iterator it2 = objs.begin();
bool deleted = 0;
while (it2 != objs.end())
{
if ((*it)->somecondition(**it2))
{
delete *it2;
*it2 = NULL;
it = objs.erase(it2);
delete *it;
*it = NULL;
it = objs.erase(it); //Will error here due to invalidating the iterator
deleted = 1;
break;
}
++it2;
}
if (!deleted)
++it;
}
The problem is that the first call to erase() might very well invalidate the other iterator. See this post for a quick summary of what gets invalidated when in various containers. I'd say the simplest solution is to first traverse the container and mark the entries to be erased but do not erase them, and then in a second scan just erase everything that was marked. For performance reasons in this second scan you should either use std::remove_if or use reverse iterator.
Working with nested iterators is tricky if you are mutating the container.
I've put together some sample code that does what you are wanting. What I'm doing is delaying the removal by setting the elements to be removed to nullptr and then removing those as we encounter them in the loops.
#include <iostream>
#include <vector>
class Example
{
public:
Example(int size) : size(size) {}
bool somecondition(const Example& other) const
{
return size == other.size;
}
int size;
};
int main()
{
std::vector<Example*> vec;
vec.push_back(new Example(1));
vec.push_back(new Example(2));
vec.push_back(new Example(3));
vec.push_back(new Example(2));
for (auto it1 = vec.begin(); it1 != vec.end();)
{
if (!*it1)
{
it1 = vec.erase(it1);
continue;
}
for (auto it2 = vec.begin(); it2 != vec.end(); ++it2)
{
if (!*it2)
{
vec.erase(it2);
// we need to start the outer loop again since we've invalidated its iterator
it1 = vec.begin();
break;
}
if (it1 != it2 && (*it1)->somecondition(**it2))
{
delete *it1;
*it1 = nullptr;
delete *it2;
*it2 = nullptr;
break;
}
}
++it1;
}
for (auto example : vec)
{
std::cout << example->size << std::endl;
}
return 0;
}

Check if map contains all the keys from another map

Check if map in C++ contains all the keys from another map answers my question but I'm not sure how we iterate through two maps at the same time.
I know how to iterate through one as shown:
typedef std::map<QString, PropertyData> TagData;
TagData original = readFileToMap("FoxHud.bak");
for (TagData::const_iterator tagIterator = original.begin(); tagIterator != original.end(); tagIterator++) {
}
Try this way:
// As std::map keys are sorted, we can do:
typedef std::map<string, int> TagData;
TagData map1;
TagData map2;
...
TagData::const_iterator map1It = map1.begin();
TagData::const_iterator map2It = map2.begin();
bool ok = true;
std::size_t cnt = 0;
while (map2It != map2.end() && map1It != map1.end()) {
if (map1It->first != map2It->first) {
map1It++;
} else {
map2It++;
cnt++;
}
}
if (cnt != map2.size()) ok = false;
cout << "OK = " << ok << endl;
This should work with maps that are not the same size, as well.
If you want to iterate the 2 maps simultaneously, you can do this:
if (map1.size() != map2.size())
; // problem
else
{
for (map<X,Y>::const_iterator it1 = map1.begin(),
it2 = map2.begin();
it1 != map1.end() && it2 != map2.end();
++it1 , ++it2)
{
// ...
}
}
Now if you want to iterate through the 2 maps at different "speeds", then a while loop to condition the increments of it1 and it2 independently would then be more appropriate. See Golgauth's answer for an example.

C++ : erase while iterating a global container

I have the following code in a function.
events is a global variable:
map<string, map<string, deque<Event> > > events;
this is my function:
void remove_events(vector<Event> c_events)
{
vector<Event>::iterator it1;
deque<Event>::iterator it2;
for(it1 = c_events.begin(); it1 != c_events.end(); ++it1)
{
it2 = events[it1->device][it1->dev_id].begin();
while(it2 != events[it1->device][it1->dev_id].end())
{
if(*it1 == *it2)
{
it2 = events[it1->device][it1->dev_id].erase(it2);
}
else ++it2;
}
}
}
c_events contains the objects I want to remove from events. However, the objects still remain in events after I tried removing them.
Let me know if you need me to post more code.