Click here to Skip to main content
15,901,368 members
Articles / Programming Languages / C++

How To Update Your Const Key Fields in a Map/Multimap

Rate me:
Please Sign up or sign in to vote.
4.96/5 (25 votes)
11 May 2009CPOL3 min read 40.5K   16   8
An introduction to the necessary steps for updating const key fields in a map/multimap

Introduction

This is not a typical C++/STL article. Instead it is an observation which I faced in my professional programming life. The idea is simple, but the effect/impact is broad. Here I am going to provide a little utility that can take back a big effort while you are doing something same.

Background

What makes a Map/Multimap beautiful? Yes you guessed it right - A key-value field type container. If you have a look in the definition of STL Map/Multimap, you'll find something like this:

C++
template <typename K, typename V,
          typename Compare = less<K>,
          typename Allocator = allocator<pair<const K, V> > >
class map; 

template <typename K, typename V,
          typename Compare = less<K>,
          typename Allocator = allocator<pair<const K, V> > >
class multimap;

So the definition looks pretty much the same, the only difference is Map never allows you to have duplicate keys, whereas Multimap allows.

Another observation/constraint is the Key field in a Map/Multimap is always constant. Once populated, you can't change/update the Key field in a conventional way, and there is a good reason behind this:

Because Map/Multimap sorts the elements based on their keys (along with the sorting functor (default is std::less, however you are free to change it to std::greater or your own functor) ) and assign the keys in a internal Red-black tree structure, if you or any generic algorithm updates the key field, that Red-Black tree will be invalid. The whole Tree might need to re-shuffle, which is quite a time consuming process. That is why STL designers takes the decision of:

You can't update the key field of a map/multimap in a conventional way.

You can't use map/multimap as a destination in a generic algorithm that updates the container.

But what if - you need to update your map/multimap's key field ? Sometimes it is damn necessary. Say initially you have a Multimap like this:

KeyValue
Allen100
Betty200
Allen200
Betty300
John500
Allen900

Now you've to update the key "Allen" with "Gary". How will you do that?

You've only one choice left:

To modify the key of an element, you must remove the element that has the old key and insert a new element that has the new key and old value.

So, how can you do that by using a Generic Function?

Using the Code

Generic functions are good, specially when if you make it detachable from a container. In order to solve the above mentioned problem: my strategy is to write a generic function like this:

C++
namespace My_Utility{
template <typename CONTAINER>
    void replace_key(CONTAINER& container,
                     const typename CONTAINER::key_type& oldKey,
                     const typename CONTAINER::key_type& newKey)
    {
        if(!container.key_comp()(oldKey,newKey) && !container.key_comp()(newKey,oldKey)){
	    return;} //Thanks to Graham for this Fix
        typename CONTAINER::iterator begin(container.find(oldKey));
        for(;;){
            if(begin != container.end()){
                container.insert(typename CONTAINER::value_type(newKey, begin->second));
                container.erase(begin); // Thanks to Graham for this potential fix
                begin = container.find(oldKey);
            }
            else{
                return;
            }
        }
    }
}

This is purely a Generic function. Given below is its description: 

  1. The template definition takes a single template parameter named CONTAINER (which can be either Map of Multimap).
  2. The argument list takes a Container, a const old key and a const new key, please see both the types of old key and newkey are fetched from the templated type. So this is purely generic.
  3. First check and return if Old key and New Key are equal, we don't need to update anything in that case. 
  4. If Old key and New key differ, then the function does as follows: 

    It finds the first iterator position where the old key exists, then it starts an infinite loop, which breaks when the iterator contains a value container.end(). Keep in mind that the find() algorithm returns cont.end() if it finds nothing, and returns the first iterator position when search is successful.

    If begin!=container.end(), then the function inserts a new row. Please see the code carefully.

    Then it erases the old row. Please see the code. It is:

    C++
    container.erase(begin);

    Then again a find() call.

Please use this code when you need to solve a similar kind of problem.  

History

  • 11th May, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Rebaca Technologies
India India
int main(){
while(!isSleeping())
{
write_code();
}
return 0;
}

Comments and Discussions

 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey28-Feb-12 18:01
professionalManoj Kumar Choubey28-Feb-12 18:01 
GeneralMy vote of 5 Pin
Rahul Rajat Singh17-Feb-12 19:15
professionalRahul Rajat Singh17-Feb-12 19:15 
GeneralMy vote of 4 Pin
John Schroedl18-May-11 5:09
professionalJohn Schroedl18-May-11 5:09 
GeneralGreat artical Pin
ed welch12-Jun-09 2:23
ed welch12-Jun-09 2:23 
GeneralRe: Great artical Pin
programmersmind13-Jun-09 8:21
programmersmind13-Jun-09 8:21 
GeneralA couple of bugs PinPopular
Graham Shanks11-May-09 1:03
Graham Shanks11-May-09 1:03 
Your code has a problem in that it requires that the key type has an equality operation defined on it. Firstly this may fail with some types since the standard associative containers only require that the key types are ordered (usually using std::less but, as you point out, can use a custom comparator). There are some key types that have a valid comparison function but not a valid equality function. In these circumstances you can successfully create a std::map or std::multimap with the key type but your code will fail to compile.

Secondly, and more subtly, the standard associative containers use equivalence rather than equality to decide if two keys are the "same". With equivalence two keys might be the same, but not identical. One of the most common examples of this is using case insensitive strings as the key - a motivating example for this would be when using a map to track data against file names under Windows, since filenames under Windows are case insensitive (i.e. foo.cpp and fOO.CpP are the same file in Windows).

To fix both these problems you need to use the following key comparison:

if(!container.key_comp()(oldKey,newKey) && !container.key_comp()(newKey,oldKey))

Although you say that the increment in the erase statement is necessary, I think that this actually introduces a bug. container.erase will invalidate the iterator and incrementing an invalid iterator is undefined behaviour. Removing the increment from this statement is OK, since you don't use begin again except to initialise it - which is safe (about the only thing that you can safely do with an invalid iterator).

Graham

Librarians rule, Ook!

GeneralRe: A couple of bugs Pin
programmersmind11-May-09 1:39
programmersmind11-May-09 1:39 
GeneralRe: A couple of bugs Pin
Graham Shanks11-May-09 2:46
Graham Shanks11-May-09 2:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.