54

I currently have lots of code which looks like this:

std::unordered_map<int,int> my_dict;
.
.
.
// If the key does exist in the dictionary
if(my_dict.count(key) == 1){
    my_dict[key] = value;
}

// If its a new key
else{
    my_dict.insert(std::make_pair(key,value));
}

Is there any way I can speed this up by just overwriting the value every time?

Jamal
  • 763
  • 7
  • 22
  • 32
user997112
  • 29,025
  • 43
  • 182
  • 361

5 Answers5

78

You just do (for map and unordered_map)

mydict[key]=value;
Joe
  • 41,484
  • 20
  • 104
  • 125
  • 1
    using `if-else` would be faster if and only if default construction of `value` is very expensive. Though, in most of the cases `operator[]` does the job. – letsBeePolite Aug 29 '17 at 16:14
28

I think it might be fastest like this:

auto it = my_dict.find(key);
if( it != my_dict.end() ) {
    it->second = value;
}
else {
    my_dict.insert(std::make_pair(key, value));
}

that way you don't modify the structure of the unordered_map if the key already exists and you only have one lookup.


Note that the above, seemingly verbose code is more flexible than:

my_dict[key] = value;

This is because Value& std::unordered_map::operator [] (const Key& key); requires Value to have default constructor (in case of value not inserted by key - construct it first, than assign/replace) that maybe not present.


Another option in case you don't need/access value afterwards:

my_dict[key] = std::move(value);

This might be better in cases when the assignment of value is expensive and benefits from move-semantics.

user16217248
  • 3,119
  • 19
  • 19
  • 37
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • 4
    @us2012 Mostly, but in the insert-case, it first inserts a default constructed value, then assigns it. The above avoids that overhead. – Daniel Frey Oct 05 '13 at 12:35
  • 5
    I'd do some serious measurement of the simple case (`foo[key]=value;`) before I go this route. – Joe Oct 05 '13 at 12:37
  • 1
    @Joe Yes. Without a profile run that shows that this is a problem, go for the more readable code. That *would* have been my answer when this question would lack the "performance" tag :) – Daniel Frey Oct 05 '13 at 12:38
  • 1
    My general experience is most people misinterpret which things are drags on their performance. I'd expect the performance difference between the OP's code and either solution here to be very small in an `unordered_map`... – Joe Oct 05 '13 at 12:41
  • 1
    @DanielFrey: The `value_type` is `pair`. Any overhead will most likely be optimised away. – Marcelo Cantos Oct 05 '13 at 12:42
  • @MarceloCantos For this example, yes. I was thinking about the generic case. – Daniel Frey Oct 05 '13 at 12:47
  • If key is not present, this performs a search twice. You should just call `insert` and check the boolean return value. – Jorge Bellon Jul 29 '20 at 12:22
10

To update for C++17, you can use:

std::unordered_map::insert_or_assign()

http://en.cppreference.com/w/cpp/container/unordered_map/insert_or_assign

htmlboss
  • 119
  • 1
  • 4
  • 11
2

All of your map functions perform a search, so you always search the map twice regardless of the key being present or not. You can leverage the fact that insert retrieves whether the insertion took place (key did not exist) or not (key exists) and act accordingly:

std::unordered_map<int,int> mydict;
bool inserted = false;
auto position = mydict.end();
std::tie(position, inserted) = mydict.insert({key, value});
if (inserted) {
  pos->second = value;
}

This would be equivalent to mydict[key] = value, because we are assigning the new value no matter what. For types where default construction is cheap I would go with operator[] instead, if that's the only thing you need to do with the map.

All insert, emplace and operator[] can perform an additional construction of value_type in different situations: insert and emplace do this before the insertion takes place and operator[] default constructs the mapped value when key is not present. Thus, they are not ideal for types whose construction/copy/move is expensive (std::thread, very large std::array...). In this case, it is more appropriate to use try_emplace instead (C++17):

std::unordered_map<int, expensive_type> mydict;
bool inserted = false;
auto position = mydict.end();
std::tie(position, inserted) = mydict.try_emplace(key, expensive_constructor_args);
if (!inserted) {
  // no expensive_type has been constructed
  // pos->second references the existing value
}
Jorge Bellon
  • 2,901
  • 15
  • 25
1

Usually you avoid extra typing by means of defining a function that saves you from typing the same again. If you don't have access to C++17's insert_or_assign(), you can implement something like this yourself:

bool InsertOrAssign(std::unordered_map& m, int key, int value) {
  // Your code or one of the suggested answers goes here
}
Serge Rogatch
  • 13,865
  • 7
  • 86
  • 158