Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Erasing elements from unordered_map in a loop

There are several answers on StackOverflow that suggest that the following loop is a fine way to erase elements from a std::unordered_map that satisfy some predicate pred:

std::unordered_map<...> m; auto it = m.begin(); while (it != m.end()) {     if (pred(*it))         it = m.erase(it);     else         ++it; } 

I'm specifically interested in C++11 (as opposed to C++14), and the following ominous note on cppreference.com suggests that the above loop depends on undefined behavior and may not work in C++11 after all:

The order of the elements that are not erased is preserved (this makes it possible to erase individual elements while iterating through the container) (since C++14)

See also Title 2356. Stability of erasure in unordered associative containers which contains a requested wording change to Working Draft N3797 item 14 on page 754 (the additional phrase beginning ", and preserve the relative order ...").

This wording is relative to N3797.

Modify [unord.req], p14 as indicated:

-14- The insert and emplace members shall not affect the validity of references to container elements, but may invalidate all iterators to the container. The erase members shall invalidate only iterators and references to the erased elements, and preserve the relative order of the elements that are not erased.

If my interpretation of the note from cppreference.com is correct, and the above loop depends on undefined behavior in C++11, what's the most efficient way to solve this problem in C++11?

like image 290
foxcub Avatar asked Jul 19 '16 21:07

foxcub


People also ask

How do you delete an unordered map value?

erasing by key: It takes a key as a parameter and erases the key and value. unordered_map. erase(const key); erase by range: It takes two iterators as a parameter and erases all the key and values present in between (including the starting iterator and excluding the end iterator).

Can you iterate over unordered_map?

Iterating over a map by using STL Iterator: By creating an iterator of std::map and initializing it to the starting of map and visiting upto the end of map we can successfully iterate over all the elements of map.

How do you reset an unordered map?

C++ Unordered_map Library - clear() Function The C++ function std::unordered_map::clear() destroys the unordered_map by removing all elements and sets the size of unordered_map to zero.

Which is more efficient map or unordered_map?

Insertion of spread keys in std::map tends to outperform std::unordered_map when map size is under 10000 elements. Insertion of dense keys in std::map doesn't present performance difference with std::unordered_map under 1000 elements. In all other situations std::unordered_map tends to perform faster.


2 Answers

To comply with C++11 you're unfortunately a bit limited in how you can tackle this. Your options basically boil down to:

  1. Iterate over the unordered_map and build a list of keys to delete like so:

    //std::unordered_map<...> mymap; std::vector<decltype(mymap)::key_type> vec; for (auto&& i : mymap)     if (/*compare i*/)         vec.emplace_back(i.first); for (auto&& key : vec)     mymap.erase(key); 
  2. Iterate over the object and reset if we find something to remove - I'd really only recommend this for small datasets. those of you who feel goto is unconditionally bad, well, this option is arguably bad.

    //std::unordered_map<...> mymap; reset: for (auto&& i : mymap)     if (/*compare i*/) {         mymap.erase(i.first);         goto reset;     } 
  3. As a somewhat out there option, you could also just create a new unordered_map and move the elements that you want to keep. This is arguably a good option when you have more to delete than to keep.

    //std::unordered_map<...> mymap; decltype(mymap) newmap; for (auto&& i : mymap)     if (/*i is an element we want*/)         newmap.emplace(std::move(i)); mymap.swap(newmap); 
like image 87
Olipro Avatar answered Sep 29 '22 13:09

Olipro


Use erase_if (c++20) instead of a loop (see https://en.cppreference.com/w/cpp/container/unordered_map/erase_if)

Example for removing odd keys from a map:

std::unordered_map<int, char> data {{1, 'a'},{2, 'b'},{3, 'c'},{4, 'd'},                                     {5, 'e'},{4, 'f'},{5, 'g'},{5, 'g'}};  const auto count = std::erase_if(data, [](const auto& item) {     auto const& [key, value] = item;     return (key & 1) == 1; }); 
like image 21
Dmitrii A Avatar answered Sep 29 '22 13:09

Dmitrii A