How to set a value in an unordered_map and find ou

2020-07-23 05:11发布

问题:

How can I efficiently and idiomatically set a value in an unordered_map and find out if a new key was added:

#include <unordered_map>
#include <string>

int main() {
  auto map = std::unordered_map<std::string, int>{{"foo", 1}, {"bar", 2}};

  map["foo"] = 3;
  // how to find out if a new key was added?
}

I can't use insert() directly because I want to overwrite the value if there is one already and insert does not do that. I can't use operator[] directly because it provides no information about whether a new key was added.

I want to avoid doing two searches in the map for performance reasons.

One trick I've seen elsewhere is to get a reference and check if that value is default constructed:

auto& value = map["foo"];
if(value == 0) {
    // am inserting a new key
}
value = 3;

But I can genuinely have default constructed values in my map so a default constructed value is not a good indication of new key.

The best I can come up with so far is:

auto size_before = map.size();
map["foo"] = 3;
if (map.size() > size_before) {
    // am inserting a new key
}

which seems ugly and it assumes that getting the size of an unordered_map is cheap (is it?).

It looks like unordered_map::insert_or_assign might be the answer to my prayers but sadly it comes in C++17 so I probably wont be able to use it for another 5 years or so. It seems like quite a common thing to want to do so I assume there must be a reasonable way of doing it currently.

回答1:

You may use std::unordered_map::insert and testing result.

With helper function:

template<typename Map, typename T>
std::pair<typename Map::iterator, bool>
insert_or_assign(Map& m, const typename Map::key_type& k, const T& t)
{
    auto p = m.insert({k, t});
    if (!p.second) {
        // overwrite previous value
        p.first->second = t;
    }
    return p;
}

and then

auto p = insert_or_assign(map, "foo", 3);
if (p.second) {
    // inserted
} else {
    // assigned
}

Live Demo



回答2:

You can use the insert() member function which will return a pair which returns a pair consisting of an iterator to the inserted element (or to the element that prevented the insertion) and a bool denoting whether the insertion took place. It would look like

if (map_name.insert(some_value).second)
    value was inserted
else
    value was not inserted

If you need to modify the value in the map when the element is not inserted then you can use

auto ret = map_name.insert(some_value);
if (!ret.second)
    *(ret.first) = some_value;

EDIT: UPDATE

Starting in C++17 std::map and std::unordered_map have the member function insert_or_assign(). This function will insert into the container if the key/value pair is not in the map and will overwrite the existing value in the container if the key already exist. The function will return a std::pair that contains an iterator to the inserted/updated element and a bool signaling if there was an insertion or not. If there was an insertion then it will be true otherwise false

std::unordered_map<std::string, int> foo = { { "foo", 1 },{ "bar", 2 } };
auto ret = foo.insert_or_assign("foo", 3);
if (ret.second)
    std::cout << "foo was inserted";
else
    std::cout << "foo already exist.  new value: " << ret.first->second;

Output:

foo already exist.  new value: 3

I cannot find an online compiler that supports C++17 at the moment that I can share the code on but you can run it here or in Microsoft Visual Studio 2015