Why C++ map.insert() doesn't overwrite - c++

In the code below:
#include <map>
#include <utility>
#include <iostream>
using namespace std;
int main(){
pair<int,int> p1(1,1);
pair<int,int> p2(1,2);
map<int,int> m;
m.insert(p1);
m.insert(p2);
cout << "Map value: "<< m.at(1) << endl;
}
It printed out : Map value: 1, why m.insert(p2) doesn't overwrite the previous entity in the map?

map.insert() only inserts if the container doesn't already contain an element with an equivalent key.
You should use operator[] instead:
m[p2.first] = p2.second;

In the std::map::insert reference it is said that:
Inserts element(s) into the container, if the container doesn't already contain an element with an equivalent key.

Update as of C++17 There is now the std::map::insert_or_assign() member function:
m.insert_or_assign(p1);
As the name suggests, if the key is already present then the value is assigned (and the key object kept) rather than erasing and freshly copy constructing the key and value. (So it's equivalent to the first of the two pre-C++17 snippets below.)
If you want an iterator pointing at the (new or updated) element, you again need to pick the value out of the returned pair. Since you're using C++17, you can now use a structured binding:
auto [it, wasInserted] = m.insert_or_assign(p1);
Before C++17 Putting together the other answers, if you want to avoid the assumption of being default constructable you get insert-with-overwrite code that looks like this:
auto itAndWasInserted = m.insert(p1);
if (!itAndWasInserted.second) {
*(itAndWasInserted.first) = p1;
}
In the above snippet, if the element is already present then the new value is assigned to it. That's usually what you want. If you instead want to construct rather than assign the new value, but still want to avoid a second seek (after you've erased the original value), you end up with this monster:
auto itAndWasInserted = m.insert(p1);
auto it = itAndWasInserted.first;
if (!itAndWasInserted.second) {
auto afterIt = m.erase(it);
auto newItAndWasInserted = m.insert(afterIt, p1); // Hint form of insert
it = newItAndWasInserted.first;
}
At the end of the code block, it is an iterator pointing at the just-inserted element.
Realistically, in most cases you probably just want to use yizzlez's suggestion of operator[], but I thought it would be good to note the theoretically best answer.

It doesn't overwrite. However if you check the return value, there is a std::pair<iterator, bool>. If bool is true, then it was inserted. If the bool is false, then it was not inserted because of a collision. At that point, you can then overwrite the data yourself by writing to the iterator.

This is supposed to happen. map.insert() will only insert elements into the container if it doesn't already contain any elements, so this will ignore the later value elements assigned to it.

Related

Can't assign to return value because function 'operator->' returns a const value

I am trying to do dfs over a graph defined as unordered_map<string,set<pair<string,int>>> g;
So here is my dfs code:
void dfs(string u){
for(auto v=g[u].begin();v!=g[u].end();v++){
if(!v->second){
cout<<v->first<<endl;
res.push_back(v->first);
v->second = 1;
dfs(v->first);
}
}
}
I am trying to change the value of v->second to 1, but I am getting an error of
cannot assign to return value because function 'operator->' returns a const value
v->second = 1;
So is there any other way to change the second value of the pair?
You cannot modify value of an element of std::set as it would break it's invariant. What you can do though is to remove existing element and insert modified one. But as you iterate over the set then such modification inside loop would be overcomplicated. So I suggest you remove all elements that satisfy your condition and then insert them all back modified.
Though it is not clear why you need int value of std::pair to be a part of the key. If that is a mistake, then just use std::unordered_map<string,std::map<string,int>> insted and then you can modify v->second without any problem (but that value would not be a part of the key anymore).
Note your loop is written quite ineffective way - you are invoking std::unordered_map::operator[] on every iteration. Instead you better get a reference to that element and use it:
void dfs(string u){
auto &gu = g[u];
for(auto v=gu.begin();v!=gu.end();v++){
...
The problem comes from begin() method of set. As stated in cpp ref:
Because both iterator and const_iterator are constant iterators (and may in fact be the same type), it is not possible to mutate the elements of the container through an iterator returned by any of these member functions.
This means the v is in fact const, so operator->() will be const version too. So you will not be able to change second.
BTW, a suggestion: use bool for visiting not int. it helps readability.

how to assign to element of set of pairs through find iterator result

I have a set of pairs and I want to change the second field of some pair that I want find first:
#include <iostream>
#include <utility>
#include <set>
int main(){
auto p=std::make_pair(2,3);
std::set<std::pair<int,int>> s{p};
auto it=s.find(p);
it->second=5; // compilation error
if(it!=s.end()) std::cout << it->second << '\n';
}
The above code fails to compile because it complains that the iterator result of find cannot be assigned to. However, I don't see why it is a const iterator.
What is wrong with the above, and how do I assign to the pair that the iterator result of find points to?
It's because set elements shouldn't be mutable, since the red-black tree that is used to implement it logically prevents us from screwing around with the actual nodes. Even without the implementation details, set elements shouldn't be mutable (as pointed out in one of the comments above), or else we could change {2,3} to {2,2}, which would be nonsensical.

C++ find and erase a multimap element

I need to add, store and delete some pairs of objects, e.g. Person-Hobby. Any person can have several hobbies and several persons can have the same hobby. So, multimap is a good container, right?
Before adding a pair I need to know, if it's not added yet. As I can see here there is no standard class-method to know, if the concrete pair e.g. Peter-Football exists in the MM. Thus, I've written a method which returns a positive integer (equal to the distance between mm.begin() and pair iterator) if the pair exists and -1 otherwise.
Then I need to delete some pair. I call my find method, which returns, some positive integer. I call myMultiMap.erase(pairIndex); but the pair is not being deleted for some reason. That is my problem. Obviously the erase method needs an iterator, not the int. The question is: how do I convert an integer to an iterator?
Thanks!
UPDATE:
I've tried this c.begin() + int_value but got an error error: no match for ‘operator+’ on this line....
Not that I favour your approach, but if the int is distance between begin() and the iterator in question, you can just use
c.begin() + int_value
or
std::advance(c.begin(), int_value)
to get the iterator. The second version is needed for iterators which are not random-access-iterators.
In the interest of your personal sanity (and the program's speed), I'd suggest you return the iterator directly in some form.
There are many possible interfaces that solve this one way or the other. What I would call the "old C way" would be returning by an out parameter:
bool find_stuff(stuff, container::iterator* out_iter) {
...
if(found && out_iter)
*out_iter = found_iter;
return found;
}
use it:
container::iterator the_iter;
if(find_stuff(the_stuff, &the_iter)) ...
or
if(find_stuff(the_stuff, 0)) // if you don't need the iterator
This is not idiomatic C++, but Linus would be pleased with it.
The second possible and theoretically sound version is using something like boost::optional to return the value. This way, you return either some value or none.
boost::optional<container::iterator> find_stuff(stuff) {
...
if(found && out_iter)
return found_iter;
return boost::none;
}
Use:
boost::optional<container::iterator> found = find_stuff(the_stuff);
if(found) {
do something with *found, which is the iterator.
}
or
if(find_stuff(the_stuff)) ...
Third possible solution would be going the std::set::insert way, ie. returning a pair consisting of a flag and a value:
std::pair<bool, container::iterator> find_stuff(stuff) {
...
return std::make_pair(found, found_iter);
}
Use:
std::pair<bool, container::iterator> found = find_stuff(the_stuff);
if(found.first) ...
Consider to change your mulitmap<Person,Hoobby> to set<pair<Person,Hobby> > - then you will not have problems you have now. Or consider to change to map<Person, set<Hobby> >. Both options will not allow to insert duplicate pairs.
Use 2 sets(not multi sets) one for hobbies and one for persons, these two acts as filters so you don't add the same person twice(or hobbie). the insert opertations on these sets gives the iterator for the element that is inserted(or the "right" iterator for element if it allready was inserted). The two iterators you get from inserting into hobbies_set and person_set are now used as key and value in a multimap
Using a third set(not multi_set) for the relation instead of a multi_map, may give the advantage of not needing to check before inserting a relation if it is allready there it will not be added again, and if it's not there it will be added. In both ways it will return an iterator and bool(tells if it was allready there or if it was added)
datastructures:
typedef std::set<Hobbie> Hobbies;
typedef std::set<Person> Persons;
typedef std::pair<Hobbies::iterator,bool> HobbiesInsertRes;
typedef std::pair<Persons::iterator,bool> PersonsInsertRes;
struct Relation {
Hobbies::iterator hobbieIter;
Persons::iterator personIter;
// needed operator<(left for the as an exercies for the reader);
};
typedef set<Relation> Relations;
Hobbies hobbies;
Persons persons;
Relations relations;
insert:
HobbiesInsertRes hres = hobbies.insert(Hobbie("foo"));
PersonsInsertRes pres = persons.insert(Person("bar"));
relations.insert(Relation(hres.first, pres.first));
// adds the relation if does not exists, if it allready did exist, well you only paid the same amount of time that you would have if you would to do a check first.
lookup:
// for a concrete Person-Hobbie lookup use
relations.find(Relation(Hobbie("foo"),Person("Bar")));
// to find all Hobbies of Person X you will need to do some work.
// the easy way, iterate all elements of relations
std::vector<Hobbie> hobbiesOfX;
Persons::iterator personX = persons.find(Person("bar"));
std::for_each(relations.begin(), relations.end(), [&hobbiesOfBar, personX](Relation r){
if(r.personIter = personX)
hobbiesOfX.push_back(r.hobbieIter);
});
// other way to lookup all hobbies of person X
Persons::iterator personX = persons.find(Person("bar"));
relations.lower_bound(Relation(personX,Hobbies.begin());
relations.upper_bound(Relation(personX,Hobbies.end());
// this needs operator< on Relation to be implemented in a way that does ordering on Person first, Hobbie second.

c++ map find() to possibly insert(): how to optimize operations?

I'm using the STL map data structure, and at the moment my code first invokes find(): if the key was not previously in the map, it calls insert() it, otherwise it does nothing.
map<Foo*, string>::iterator it;
it = my_map.find(foo_obj); // 1st lookup
if(it == my_map.end()){
my_map[foo_obj] = "some value"; // 2nd lookup
}else{
// ok do nothing.
}
I was wondering if there is a better way than this, because as far as I can tell, in this case when I want to insert a key that is not present yet, I perform 2 lookups in the map data structures: one for find(), one in the insert() (which corresponds to the operator[] ).
Thanks in advance for any suggestion.
Normally if you do a find and maybe an insert, then you want to keep (and retrieve) the old value if it already existed. If you just want to overwrite any old value, map[foo_obj]="some value" will do that.
Here's how you get the old value, or insert a new one if it didn't exist, with one map lookup:
typedef std::map<Foo*,std::string> M;
typedef M::iterator I;
std::pair<I,bool> const& r=my_map.insert(M::value_type(foo_obj,"some value"));
if (r.second) {
// value was inserted; now my_map[foo_obj]="some value"
} else {
// value wasn't inserted because my_map[foo_obj] already existed.
// note: the old value is available through r.first->second
// and may not be "some value"
}
// in any case, r.first->second holds the current value of my_map[foo_obj]
This is a common enough idiom that you may want to use a helper function:
template <class M,class Key>
typename M::mapped_type &
get_else_update(M &m,Key const& k,typename M::mapped_type const& v) {
return m.insert(typename M::value_type(k,v)).first->second;
}
get_else_update(my_map,foo_obj,"some value");
If you have an expensive computation for v you want to skip if it already exists (e.g. memoization), you can generalize that too:
template <class M,class Key,class F>
typename M::mapped_type &
get_else_compute(M &m,Key const& k,F f) {
typedef typename M::mapped_type V;
std::pair<typename M::iterator,bool> r=m.insert(typename M::value_type(k,V()));
V &v=r.first->second;
if (r.second)
f(v);
return v;
}
where e.g.
struct F {
void operator()(std::string &val) const
{ val=std::string("some value")+" that is expensive to compute"; }
};
get_else_compute(my_map,foo_obj,F());
If the mapped type isn't default constructible, then make F provide a default value, or add another argument to get_else_compute.
There are two main approaches. The first is to use the insert function that takes a value type and which returns an iterator and a bool which indicate if an insertion took place and returns an iterator to either the existing element with the same key or the newly inserted element.
map<Foo*, string>::iterator it;
it = my_map.find(foo_obj); // 1st lookup
my_map.insert( map<Foo*, string>::value_type(foo_obj, "some_value") );
The advantage of this is that it is simple. The major disadvantage is that you always construct a new value for the second parameter whether or not an insertion is required. In the case of a string this probably doesn't matter. If your value is expensive to construct this may be more wasteful than necessary.
A way round this is to use the 'hint' version of insert.
std::pair< map<foo*, string>::iterator, map<foo*, string>::iterator >
range = my_map.equal_range(foo_obj);
if (range.first == range.second)
{
if (range.first != my_map.begin())
--range.first;
my_map.insert(range.first, map<Foo*, string>::value_type(foo_obj, "some_value") );
}
The insertiong is guaranteed to be in amortized constant time only if the element is inserted immediately after the supplied iterator, hence the --, if possible.
Edit
If this need to -- seems odd, then it is. There is an open defect (233) in the standard that hightlights this issue although the description of the issue as it applies to map is clearer in the duplicate issue 246.
In your example, you want to insert when it's not found. If default construction and setting the value after that is not expensive, I'd suggest simpler version with 1 lookup:
string& r = my_map[foo_obj]; // only lookup & insert if not existed
if (r == "") r = "some value"; // if default (obj wasn't in map), set value
// else existed already, do nothing
If your example tells what you actually want, consider adding that value as str Foo::s instead, you already have the object, so no lookups would be needed, just check if it has default value for that member. And keep the objs in the std::set. Even extending class FooWithValue2 may be cheaper than using map.
But If joining data through the map like this is really needed or if you want to update only if it existed, then Jonathan has the answer.

std::map difference between index and insert calls

What is the difference between the index overloaded operator and the insert method call for std::map?
ie:
some_map["x"] = 500;
vs.
some_map.insert(pair<std::string, int>("x", 500));
I believe insert() will not overwrite an existing value, and the result of the operation can be checked by testing the bool value in the iterator/pair value returned
The assignment to the subscript operator [] just overwrites whatever's there (inserting an entry if there isn't one there already)
Either of the insert and [] operators can cause issues if you're not expecting that behaviour and don't accommodate for it.
Eg with insert:
std::map< int, std::string* > intMap;
std::string* s1 = new std::string;
std::string* s2 = new std::string;
intMap.insert( std::make_pair( 100, s1 ) ); // inserted
intMap.insert( std::make_pair( 100, s2 ) ); // fails, s2 not in map, could leak if not tidied up
and with [] operator:
std::map< int, std::string* > intMap;
std::string* s1 = new std::string;
std::string* s2 = new std::string;
intMap[ 100 ] = s1; // inserted
intMap[ 100 ] = s2; // inserted, s1 now dropped from map, could leak if not tidied up
I think those are correct, but haven't compiled them, so may have syntax errors
For a map, the former (operator[]) expression will always replace the value part of the key-value pair with the new supplied value. A new key-value pair will be inserted if one doesn't already exist.
In contrast, insert will only insert a new key-value pair if a key-value pair with the supplied key part does not already exist in the map.
In addition to the fact that map::operator[] will replace an existing value is that operator[] map::will create and add to the map a default existing value to replace before the replacement occurs (the map::operator[]() call has to return a reference to something). For items that are expensive to create this could be a performance issue.
See "Item 24: Choose carefully between map::operator[] and map::insert when efficiency is important" in Scott Meyers' Effective STL.
The insert method inserts into the map, while the overloaded index operator will return the element with the key key_value if it is in the map, if it is not already in the map then it will insert it.
Just to add to Michael Burr's answer, the book you should be looking for is Scott Meyer's <Effective STL> not the <Effective C++>, as Michael has linked it wrongly.
the expression
m[k] = v;
  checks to see if the key k is already in the map. If not, it’s
added, along with v as its corresponding value. If k is already in the
map, its associated value is updated to v.
(when adding new k-v)
We first default-construct a <value obj>, then we immediately assign
it a new value. If it’s measurably more efficient to construct a
<value obj> with the value we want instead of default-constructing the
<value obj> and then doing the assignment, we’d be better off replacing our
use of operator[] (including its attendant construction plus
assignment) with a straightforward call to insert:
(updating existing k's v)
we now understand that when an “add” is performed, insert is more
efficient than operator[]. The situation is reversed when we do an
update, i.e., when an equivalent key is already in the
map.