I have a container of type unordered_map and I was wanting confirmation of which version I should use if I want to add an element to the map. I want it to overwrite the old value with the new presented if it exists and just add it if it does not.
I see that insert adds the element if it exits and also returns a pair of iterator and bool where the bool indicates if the insert is successful. I also see that operator[] adds the element if it does not exist and overwrites it if it does.
My question is basically if I should I be using operator[] for this purpose or are there any gotchas that I haven't considered. Also if my perception of these methods is wrong, please correct me.
here is what I was going to do. Data is a scoped enum of storage type int
void insertData(const Data _Data, const int _value)
{
int SC_val = static_cast<int>(_Data);
//sc val is now the integer value of the Data being added
//returns a pair of iterator and bool indicating whether the insert was successful
auto ret = baseData.insert(std::pair<int,int>(SC_val,_value));
if (ret.second == false)
{//if the insert was not successful(key already exists)
baseData[ret.first->first] = _value;
}
}
or should I just do
int index = static_cast<int>(_Data);
baseData[index] = _value;
I am leaning towards the operator[] version as I see no real difference and it is much less code. Please advise and thank you all in advance.
insert and operator[] are both very useful methods. They appear similar, however, the details make them very different.
operator[]
Returns a reference to the element you are searching for. When no element exists, it creates a new default element. (So requires default constructor)
When used to insert an element: myMap[key] = value;, the value will override the old value for the key.
insert
Returns an iterator and a bool. The iterator is to the element. The bool indicates if a new element was inserted (true), or it already contained an element for the key (false).
Using insert doesn't require a default constructor.
When used to insert a new element: myMap.insert({key, value});, the old value does not get updated if key already exists in the map.
insert_or_assign
Tnx to Marc Glisse who mentioned it in the comments.
This method is similar to insert. The difference is in the behavior when the element already exists, in which case it will override the existing element.
Returns an iterator and a bool. The iterator is to the element. The bool indicates if a new element was inserted (true), or it already contained an element for the key (false).
Using insert_or_assign doesn't require a default constructor.
When used to insert a new element: myMap.insert({key, value});, the old value gets updated if key already exists in the map.
Building your map
Your use-case inserts data into the map and assumes that the key doesn't exist.
Writing baseData[index] = _value; will exactly do what you want.
However, if I would have to write it, I would go with the insert variant:
auto successfulInsert = baseData.emplace(SC_val, _value).second;
assert(successfulInsert && "Value has been inserted several times.");
Just using operator [] perfectly fits for your case.
FYI: Quote from cppreference.com std::unordered_map:
std::unordered_map::operator[]
Returns a reference to the value that is mapped to a key equivalent to key, performing an insertion if such key does not already exist.
I see no real difference and it is much less code.
You're right!
It seems that you want to insert data only when it is not exist in the baseData.
You can use count() to check if the data is in the map like this:
int index = static_cast<int>(_Data);
if(!baseData.count(index))
{
baseData[index] = _value
}
Related
I have nested map of type:
std::map<int,std::map<pointer,pointer>>
I am iterating over the map each time/per frame and doing updates on it.So basically I have 2 nested if loops.
i have an array and i need to sort the data with 2 attributes. First attribute is integer which is the first key, then second attribute is a pointer which is a key of nested map inside the main map. so my code is something like:
iterator = outermap.find();
if(iterator!=outermap.end()){
value = iterator->second;
it1 = value.find();
if(it1!=value.end(){
value1 = it1->second;
// do something
}
else{
// do something and add new value
}
}
else {
// do something and add the values
}
This is really slow and causing my application to drop frame rate. Is there any alternative to this? Can we use hash codes and linked list to achieve the same?
You can use std::unordered_map, it will hash the keys so finds complete faster. Using value = iterator->second is copying your entire map to the 'value' variable. Using a reference avoids unnecessary copying and is better for performance, eg: auto & value = iterator->second.
Also std::map is guaranteed to be ordered. This can be used to your advantage since your keys are integers for the outermost map.
Firstly, your question is a bit vague, so this may or may not fit your problem.
Now, you have a map<int, map<pointer, pointer>>, but you never operate on the inner map itself. All you do is look up a value by an int and a pointer. This is also exactly what you should do instead, use an aggregate of those two as key in a map. The type for that is pair<int, pointer>, the map then becomes a map<pair<int, pointer>, pointer>.
One more note: You seem to know the keys to search in the map in advance. If the check whether the element exists is not just for safety, you could also use the overloaded operator[] of the map. The lookup then becomes outermap[ikey][pkey] and returns a default-initialized pointer (so probably a null pointer, it pointer really is a pointer). For the suggested combined map, the lookup would be outermap[make_pair(ikey, pkey)].
I am using QHash in C++ to store some simple key and value pairs. In my case the key is an integer, so is the value. To add a new key/value pair to the hash, this is my syntax:
QHash<int, int> myhash;
int key = 5;
int value = 87;
myhash.insert(key,value);
qDebug() << "key 5 value = " << myhash.value(5); // outputs 87
How can I update an existing key-value par? What is the syntax?
T & QHash::operator[](const Key & key)
Returns the value associated with the key as a modifiable reference.
You can do the following:
myhash[5] = 88;
According to the documentation if the key is not present, a default value is constructed and returned. This means that depending on the scenario you might want to consider first making sure that the key is actually present (for example if you are iterating through the keys in a for/foreach loop and using the retrieved key to call the [] operator, you will avoid this issue) or check the retrieved value and whether it is a default one or not.
From docs: If you call insert() with a key that already exists in the QHash, the previous value is erased. For example:
hash.insert("plenty", 100);
hash.insert("plenty", 2000);
// hash.value("plenty") == 2000
Operator[] works too in this case. But be aware in some other cases. From docs:
In general, we recommend that you use contains() and value() rather than operator for looking up a key in a hash. The reason is that operator silently inserts an item into the hash if no item exists with the same key (unless the hash is const).
I need to modify an object that has already been inserted into a set. This isn't trivial because the iterator in the pair returned from an insertion of a single object is a const iterator and does not allow modifications. So, my plan was that if an insert failed I could copy that object into a temporary variable, erase it from the set, modify it locally and then insert my modified version.
insertResult = mySet.insert(newPep);
if( insertResult.second == false )
modifySet(insertResult.first, newPep);
void modifySet(set<Peptide>::iterator someIter, Peptide::Peptide newPep) {
Peptide tempPep = (*someIter);
someSet.erase(someIter);
// Modify tempPep - this does not modify the key
someSet.insert(tempPep);
}
This works, but I want to make my insert more efficient. I tried making another iterator and setting it equal to someIter in modifySet. Then after deleting someIter I would still have an iterator to that location in the set and I could use that as the insertion location.
void modifySet(set<Peptide>::iterator someIter, Peptide::Peptide newPep) {
Peptide tempPep = (*someIter);
anotherIter = someIter;
someSet.erase(someIter);
// Modify tempPep - this does not modify the key
someSet.insert(anotherIter, tempPep);
}
However, this results in a seg fault. I am hoping that someone can tell me why this insertion fails or suggest another way to modify an object that has already been inserted into a set.
The full source code can be viewed at github.
I agree with Peter that a map is probably a better model of what you are doing, specifically something like map<pep_key, Peptide::Peptide>, would let you do something like:
insertResult = myMap.insert(std::make_pair(newPep.keyField(), newPep));
if( insertResult.second == false )
insertResult.first->second = newPep;
To answer your question, the insert segfaults because erase invalidates an iterator, so inserting with it (or a copy of it) is analogous to dereferencing an invalid pointer. The only way I see to do what you want is with a const_cast
insertResult = mySet.insert(newPep);
if( insertResult.second == false )
const_cast<Peptide::Peptide&>(*(insertResult.first)) = newPep;
the const_cast approach looks like it will work for what you are doing, but is generally a bad idea.
I hope it isn't bad form to answer my own question, but I would like it to be here in case someone else ever has this problem. The answer of why my attempt seg faulted was given my academicRobot, but here is the solution to make this work with a set. While I do appreciate the other answers and plan to learn about maps, this question was about efficiently re-inserting into a set.
void modifySet(set<Peptide>::iterator someIter, Peptide::Peptide newPep) {
if( someIter == someSet.begin() ) {
Peptide tempPep = (*someIter);
someSet.erase(someIter);
// Modify tempPep - this does not modify the key
someSet.insert(tempPep);
}
else {
Peptide tempPep = (*someIter);
anotherIter = someIter;
--anotherIter;
someSet.erase(someIter);
// Modify tempPep - this does not modify the key
someSet.insert(anotherIter, tempPep);
}
}
In my program this change dropped my run time by about 15%, from 32 seconds down to 27 seconds. My larger data set is currently running and I have my fingers crossed that the 15% improvement scales.
std::set::insert returns a pair<iterator, bool> as far as I know. In any case, directly modifying an element in any sort of set is risky. What if your modification causes the item to compare equal to another existing item? What if it changes the item's position in the total order of items in the set? Depending on the implementation, this will cause undefined behaviour.
If the item's key remains the same and only its properties change, then I think what you really want is a map or an unordered_map instead of a set.
As you realized set are a bit messy to deal with because you have no way to indicate which part of the object should be considered for the key and which part you can modify safely.
The usual answer is to use a map or an unordered_map (if you have access to C++0x) and cut your object in two halves: the key and the satellite data.
Beware of the typical answer: std::map<key_type, Peptide>, while it seems easy it means you need to guarantee that the key part of the Peptide object always match the key it's associated with, the compiler won't help.
So you have 2 alternatives:
Cut Peptide in two: Peptide::Key and Peptide::Data, then you can use the map safely.
Don't provide any method to alter the part of Peptide which defines the key, then you can use the typical answer.
Finally, note that there are two ways to insert in a map-like object.
insert: insert but fails if the value already exists
operator[]: insert or update (which requires creating an empty object)
So, a solution would be:
class Peptide
{
public:
Peptide(int const id): mId(id) {}
int GetId() const;
void setWeight(float w);
void setLength(float l);
private:
int const mId;
float mWeight;
float mLength;
};
typedef std::unordered_map<int, Peptide> peptide_map;
Note that in case of update, it means creating a new object (default constructor) and then assigning to it. This is not possible here, because assignment means potentially changing the key part of the object.
std::map will make your life a lot easier and I wouldn't be surprised if it outperforms std::set for this particular case. The storage of the key might seem redundant but can be trivially cheap (ex: pointer to immutable data in Peptide with your own comparison predicate to compare the pointee correctly). With that you don't have to fuss about with the constness of the value associated with a key.
If you can change Peptide's implementation, you can avoid redundancy completely by making Peptide into two separate classes: one for the key part and one for the value associated with the key.
I have a map of objects and I want to update the object mapped to a key, or create a new object and insert into the map. The update is done by a different function that takes a pointer to the object (void update(MyClass *obj))
What is the best way to "insert or update" an element in a map?
The operator[]
With something like the following snippet:
std::map<Key, Value>::iterator i = amap.find(key);
if (i == amap.end())
amap.insert(std::make_pair(key, CreateFunction()));
else
UpdateFunction(&(i->second));
If you want to measure something that might improve performance you might want to use .lower_bound() to find where an entry and use that as a hint to insert in the case where you need to insert a new object.
std::map<Key, Value>::iterator i = amap.lower_bound(key);
if (i == amap.end() || i->first != key)
amap.insert(i, std::make_pair(key, CreateFunction()));
// Might need to check and decrement i.
// Only guaranteed to be amortized constant
// time if insertion is immediately after
// the hint position.
else
UpdateFunction(&(i->second));
something like:
map<int,MyClass*> mymap;
map<int,MyClass*>::iterator it;
MyClass* dummy = new MyClass();
mymap.insert(pair<int,MyClass*>(2,dummy));
it = mymap.find(2);
update(it.second);
here a nice reference link
The operator[] already does, what you want. See the reference for details.
The return value of insert is "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."
Therefore you can simply do
auto result = values.insert({ key, CreateFunction()});
if (!result.second)
UpdateFunction(&(result.first->second));
NOTE:
Since your question involved raw pointers, and you said you wanted your Update function to take a pointer, I have made that assumption in my snippet. Assume that CreateFunction() returns a pointer and UpdateFunction() expects a pointer.
I'd strongly advise against using raw pointers though.
In C++17, function insert_or_assign insert if not existing and update if there.
I have just found out that when I search a map like :
std::map<std::string, int> aMap;
the keys I search start to be part of the map. In the case above the values are stored as zeros. In case of pointers it stores the values as 0 valued pointers
I am doing the search with the [] operator, like in :
int a = aMap["some key"];
Can you confirm this ? I think I have misinterpreted the [] operator. Is it doing an assignment ?!
Where can I find STL documentation of these kind of "features" ?
Are you searching it with the [] operator? If so, then yes, this is the defined behaviour.
You should use the 'find' method if you don't want this behaviour.
A good reference for the STL is the Nicolai Josuttis book.
If you look in the map using the [] operator, then, yes, you will generate default objects. If you use 'find', on the other hand, it does not. This is because the [] operator MUST return a reference to an object in the map, so it has no choice but to generate one if one is not already there.
The reasoning behind this is:
[] is defined as
T& operator[](KEY k)
A Reference can never be NULL, so some value must be returned. STL solves this by inserting a default initialized element.
int a = aMap["some key"];
Here map checks to see if the key "some key" is already exists the map:
if yes then the reference of
the value corresponding to the "Some key" is returned.
If "some key" doesnot exists in the map then the
key "Some Key" is inserted in the map with default value.
reference of the newly inserted value will be
returned.
The correct way of testing whether a key exits in map (without adding the key to map) is:
std::map<key,value>::iterator iter = myMap.find("Some Key");
if( iter != myMap.end())
{
//key exists
}
else
{
//no key
}
How do you search???
if(!aMap[key]) // not found
This is not correct, once you access a map via operator[] the apropriate place is created and the reference is returned.
You need to use
if(aMao.find(key)==aMap.end()) // not found
It sounds like you're using the bracket operator, i.e.
if (aMap["string"] == something)
Don't do that. Instead, use map::find.
The bracket operator will automatically insert the key into the map if it doesn't exist, using the default for the value part.
If by search you mean using operator[], like this:
if ( m["foo"] == 42 ) {
// found
}
else {
// not
}
then yes, that will create an entry for "foo" if it does not already exist. For this reason, you should generally avoid using operator[] for maps and use the named function sfind() and insert() instead.
As for where to find out information on this behaviour, the best book on the standard library is The C++ Standard Library by Nicolai Josuttis.