tyrion
tyrion

Reputation: 742

How to replace <key, value> in a std multimap

Rehrasing the question - Question rephrased - I have a requirement where I need to replace a pair with a new key and value. Consider this -

#include <map>
#include <string>
#include <iostream>

using namespace std;

int main()
{
    std::multimap<unsigned int, std::string> mymap;

    mymap.insert(std::multimap<unsigned int, std::string>::value_type(0, "A1"));
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(0, "A2"));
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(2, "C1"));
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(2, "C2"));
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(1, "B1"));
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(1, "B2"));
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(1, "B3"));

    std::pair<std::multimap<unsigned int, std::string>::iterator, std::multimap<unsigned int, std::string>::iterator> pr = mymap.equal_range(1);

    std::multimap<unsigned int, std::string>::iterator it;
    for (it=pr.first; it!=pr.second; ++it)
    {
        unsigned int key = it->first;
    key = key+10;

        std::string val = it->second;
        val = "X" + val;
        mymap.erase(it);
        mymap.insert(std::multimap<unsigned int, std::string>::value_type(key, val));

    }

    for ( it=mymap.begin() ; it != mymap.end(); it++ )
    {
        cout << (*it).first << " => " << (*it).second << endl;
    }

    return 0;

}

The program crashes on visual studio 2008 since the iterator is invalidated.

I expect it to be:

0 => A1
0 => A2
2 => C1
2 => C2
11 => XB1
11 => XB2
11 => XB3

The idea is I want to replace an existing entry in the map with a new entry.

What am I doing wrong? Any help very much appreciated.

Upvotes: 1

Views: 9249

Answers (3)

Hack06
Hack06

Reputation: 1092

With C++17 there's an elegant way of doing it, with the special function extract() : https://en.cppreference.com/w/cpp/container/multimap/extract

The example at the bottom of the page does exactly that (copied here for a faster reference) :

#include <algorithm>
#include <iostream>
#include <string_view>
#include <map>
 
void print(std::string_view comment, const auto& data)
{
    std::cout << comment;
    for (auto [k, v] : data)
        std::cout << ' ' << k << '(' << v << ')';
 
    std::cout << '\n';
}
 
int main()
{
    std::multimap<int, char> cont{{1, 'a'}, {2, 'b'}, {3, 'c'}};
 
    print("Start:", cont);
 
    // Extract node handle and change key
    auto nh = cont.extract(1);
    nh.key() = 4;
 
    print("After extract and before insert:", cont);
 
    // Insert node handle back
    cont.insert(std::move(nh));
 
    print("End:", cont);
}

Output:

Start: 1(a) 2(b) 3(c)
After extract and before insert: 2(b) 3(c)
End: 2(b) 3(c) 4(a)

Upvotes: 1

Derek Ledbetter
Derek Ledbetter

Reputation: 4895

The trick is to advance the iterator first, then erase a copy of the iterator.

std::multimap<unsigned int, std::string>::iterator it = pr.first;
while (it != pr.second)
{
    unsigned int key = it->first;
    key = key+10;

    std::string val = it->second;
    val = "X" + val;

    std::multimap<unsigned int, std::string>::iterator itCopy = it;
    ++it;
    mymap.erase(itCopy);
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(key, val));
}

In C++11, you can do this:

std::multimap<unsigned int, std::string>::iterator it = pr.first;
while (it != pr.second)
{
    unsigned int key = it->first;
    key = key+10;

    std::string val = it->second;
    val = "X" + val;

    it = mymap.erase(it);
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(key, val));
}

By the way, because this code is increasing the key each time, it will process every element over and over again.

Upvotes: 6

Jakob S.
Jakob S.

Reputation: 1891

If you erase the entry where the iterator is, all links to the rest of the multimap are potentially lost.

(new) solution:

Simply do the "erase" after you inserted the new entries:

std::multimap<unsigned int, std::string>::iterator it;
for (it=pr.first; it!=pr.second; ++it)
{
    unsigned int key = it->first;
    key = key+10;

    std::string val = it->second;
    val = "X" + val;
    mymap.insert(std::multimap<unsigned int, std::string>::value_type(key, val));
}
mymap.erase(pr.first, pr.second);

Upvotes: 2

Related Questions