Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter items from a std::map? [duplicate]

Tags:

c++

stl

boost

I have roughly the following code. Could this be made nicer or more efficient? Perhaps using std::remove_if? Can you remove items from the map while traversing it? Can we avoid using the temporary map?

typedef std::map<Action, What> Actions;
static Actions _actions;

bool expired(const Actions::value_type &action)
{
  return <something>;
}

void bar(const Actions::value_type &action)
{
  // do some stuff
}

void foo()
{
  // loop the actions finding expired items
  Actions actions;
  BOOST_FOREACH(Actions::value_type &action, _actions)
  {
    if (expired(action))
      bar(action);
    else
      actions[action.first]=action.second;
    }
  }
  actions.swap(_actions);
}
like image 951
1800 INFORMATION Avatar asked Oct 07 '08 21:10

1800 INFORMATION


People also ask

Can std::map have duplicates?

a map will not throw any compile/run time error while inserting value using duplicate key. but while inserting, using the duplicate key it will not insert a new value, it will return the same exiting value only. it will not overwrite.

Can map C++ have duplicates?

Multi-map in C++ is an associative container like map. It internally store elements in key value pair. But unlike map which store only unique keys, multimap can have duplicate keys.

How do you remove items from std::map?

The standard approach to remove elements from a map is using the std::map::erase member function. It offers several overload versions. The first overload version accepts an iterator pointing to an element that needs to be removed from the map.


4 Answers

A variation of Mark Ransom algorithm but without the need for a temporary.

for(Actions::iterator it = _actions.begin();it != _actions.end();)
{
    if (expired(*it))
    {
        bar(*it);
        _actions.erase(it++);  // Note the post increment here.
                               // This increments 'it' and returns a copy of
                               // the original 'it' to be used by erase()
    }
    else
    {
        ++it;  // Use Pre-Increment here as it is more effecient
               // Because no copy of it is required.
    }
}
like image 192
Martin York Avatar answered Oct 04 '22 10:10

Martin York


You could use erase(), but I don't know how BOOST_FOREACH will handle the invalidated iterator. The documentation for map::erase states that only the erased iterator will be invalidated, the others should be OK. Here's how I would restructure the inner loop:

Actions::iterator it = _actions.begin();
while (it != _actions.end())
{
  if (expired(*it))
  {
    bar(*it);
    Actions::iterator toerase = it;
    ++it;
    _actions.erase(toerase);
  }
  else
    ++it;
}
like image 31
Mark Ransom Avatar answered Oct 04 '22 10:10

Mark Ransom


Something that no one ever seems to know is that erase returns a new, guaranteed-to-be-valid iterator, when used on any container.

Actions::iterator it = _actions.begin();
while (it != _actions.end())
{
  if (expired(*it))
  {
    bar(*it);
    it = _actions::erase(it);
  }
  else
    ++it;
}

Storing actions.end() is probably not a good plan in this case since iterator stability is not guaranteed, I believe.

like image 25
coppro Avatar answered Oct 04 '22 11:10

coppro


If the idea is to remove expired items, why not use map::erase? This way you only have to remove elements you don't need anymore, not rebuild an entire copy with all the elements that you want to keep.

The way you would do this is to save off the iterators pointing to the elements you want to erase, then erase them all after the iteration is over.

Or, you can save off the element that you visited, move to the next element, and then erase the temporary. The loop bounds get messed up in your case though, so you have to fine tune the iteration yourself.

Depending on how expired() is implemented, there may be other better ways. For example if you are keeping track of a timestamp as the key to the map (as expired() implies?), you can do upper_bound on the current timestamp, and all elements in the range [ begin(), upper_bound() ) need to be processed and erased.

like image 37
Greg Rogers Avatar answered Oct 04 '22 10:10

Greg Rogers