How to set a value in an unordered_map and find out if a new key was added

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?
}

Я не могу использовать insert() directly because I want to overwrite the value if there is one already and insert does not do that. Я не могу использовать operator[] напрямую, потому что он не предоставляет информации о том, был ли добавлен новый ключ.

Я хочу избежать двух поисков на карте по соображениям производительности.

Один прием, который я видел в другом месте, - это получить ссылку и проверить, построено ли это значение по умолчанию:

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

Но на моей карте действительно могут быть значения по умолчанию, поэтому значение по умолчанию не является хорошим показателем нового ключа.

Лучшее, что я могу придумать, это:

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

который кажется уродливым, и это предполагает, что получение размера unordered_map это дешево (правда?).

Это выглядит как unordered_map::insert_or_assign может быть ответом на мои молитвы, но, к сожалению, это происходит в C++17, так что я, вероятно, не смогу использовать его еще 5 лет или около того. Кажется, что это довольно обычное дело, поэтому я предполагаю, что в настоящее время должен быть разумный способ сделать это.

2 ответа

Решение

Вы можете использовать std::unordered_map::insert и результат тестирования.

С функцией помощника:

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;
}

а потом

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

Live Demo

Вы можете использовать insert() функция-член, которая будет возвращать pair который возвращает пару, состоящую из итератора для вставленного элемента (или для элемента, который предотвратил вставку) и логическое значение, обозначающее, произошла ли вставка. Это будет выглядеть

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

Если вам нужно изменить значение на карте, когда элемент не вставлен, вы можете использовать

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

РЕДАКТИРОВАТЬ: ОБНОВЛЕНИЕ

Начиная с C++17 std::map а также std::unordered_map иметь функцию-член insert_or_assign(), Эта функция будет вставлена ​​в контейнер, если пара ключ / значение отсутствует на карте, и перезапишет существующее значение в контейнере, если ключ уже существует. Функция вернет std::pair который содержит итератор для вставленного / обновленного элемента и сигнал bool, если была вставка или нет. Если была вставка то будет true иначе 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;

Выход:

foo already exist.  new value: 3

Я не могу найти онлайн-компилятор, который поддерживает C++17 в данный момент, на котором я могу поделиться кодом, но вы можете запустить его здесь или в Microsoft Visual Studio 2015

Другие вопросы по тегам