C++

C++ map sort by Key

A map consists of key/value pairs. Each pair is an element. All keys in a map are unique. A map can be sorted by keys. The sorting can be ascending or descending. Ascending is the default. Sorting in a map is not always straightforward. It needs a comparison function object. If the comparison object is ignored, default sorting takes place.

If the keys are constant-pointers-to-characters, the map is sorted by the key pointers, and not by the key string literals. This is hardly what anybody wants. Consider the following key/value pairs of fruits and their outer colors:

    "plum" => "purple"
    "blackberry" => "dark blue-black"
    "watermelon" => "green"
    "apricot", => "orange"
     "papaya" => "orange"
    "banana" => "yellow"

The fruits are the keys, and the colors are the values. This list of elements (key/value pairs) is not sorted. The following program creates a map of this list as it is and displays it as it is, unsorted by string literals:

    #include <iostream>
    #include <map>
    using namespace std;
   
    int main()
    {
        map<const char*, const char*> mp;

        mp["plum"] = "purple";
        mp["blackberry"] = "dark blue-black";
        mp["watermelon"] = "green";
        mp["apricot"] = "orange";
        mp["papaya"] = "orange";
        mp["banana"] = "yellow";

        for (map<const char*, const char*>::iterator it = mp.begin(); it != mp.end(); it++)
            cout << it->first << " => " << it->second << endl;

        return 0;
    }

The output is:

    plum => purple
    blackberry => dark blue-black
    watermelon => green
    apricot => orange
    papaya => orange
    banana => yellow

unsorted by string literals, but sorted by pointers. To use a map in a C++ program, the map library has to be included with an include directive.

Another way of creating the above simple map, is as follows:

    #include <iostream>
    #include <map>
    using namespace std;
   
    int main()
    {
        map<const char*, const char*> mp({{"plum","purple"}, {"blackberry","dark blue-black"}, {"watermelon","green"}, {"apricot","orange"}, {"papaya","orange"}, {"banana","yellow"}});

        for (map<const char*, const char*>::iterator it = mp.begin(); it != mp.end(); it++)
            cout << it->first << " => " << it->second << endl;

        return 0;
    }

The output is:

    plum => purple
    blackberry => dark blue-black
    watermelon => green
    apricot => orange
    papaya => orange
    banana => yellow

unsorted by string literals, though sorted by pointers. If the keys were integers, the output would have been sorted by keys. In practice, the keys of many maps are string literals. This article explains how keys of string literals can sort a map.

Article Content

Sort During Creation

The full template for the map construction is:

    template<class Key, class T, class Compare = less<Key>, class Allocator = allocator<pair<const Key, T>>> class map;

The classes, Compare and Allocator, have default values. That is, they have default specialization, which does not have to be typed in the map declarations (instantiations). What is of interest here is the comparison class. The name of the class is Compare, and the default specialization is “less<Key>”. “less<Key” means sort in ascending order. Another option is “greater<Key>”, which means sort descending.

A map is normally created sorted by keys during creation. If the keys are const char*, then the pointers to the quoted literal strings will be sorted, not the literal texts. To have strings as keys sorted during creation, the strings have to be literals of string objects instantiated from the string class. This means the string library has to be included, as well as the map library.

Creating Ascending

In the following program, the map is created, sorted ascending:

    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;
   
    int main()
    {
        map<string, const char*, less<string>> mp;

        mp["plum"] = "purple";
        mp["blackberry"] = "dark blue-black";
        mp["watermelon"] = "green";
        mp["apricot"] = "orange";
        mp["papaya"] = "orange";
        mp["banana"] = "yellow";

        for (map<string, const char*>::iterator it = mp.begin(); it != mp.end(); it++)
            cout << it->first << " => " << it->second << endl;

        return 0;
    }

The output is:

    apricot => orange
    banana => yellow
    blackberry => dark blue-black
    papaya => orange
    plum => purple
    watermelon => green

Even if less<string> were omitted from the template, the sorting would still have been ascending because less is the default.

Creating Descending

In order to create a map, such that it is sorted in descending order by keys, the Compare specialization has to be coded. The following program illustrates this:

    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;
   
    int main()
    {
        map<string, const char*, greater<string>> mp;

        mp["plum"] = "purple";
        mp["blackberry"] = "dark blue-black";
        mp["watermelon"] = "green";
        mp["apricot"] = "orange";
        mp["papaya"] = "orange";
        mp["banana"] = "yellow";

        for (map<string, const char*>::iterator it = mp.begin(); it != mp.end(); it++)
            cout << it->first << " => " << it->second << endl;

        return 0;
    }

The output is:

    watermelon => green
    plum => purple
    papaya => orange
    blackberry => dark blue-black
    banana => yellow
    apricot => orange

Producing a Range Descending

A range of a map can be produced in descending order. This involves creating a second map, which is a range from the first map. The following program illustrates this:

    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;
   
    int main()
    {
        map<string, const char*> mp;

        mp["plum"] = "purple";
        mp["blackberry"] = "dark blue-black";
        mp["watermelon"] = "green";
        mp["apricot"] = "orange";
        mp["papaya"] = "orange";
        mp["banana"] = "yellow";

        map<string, const char*>::iterator itB = mp.begin();
        itB++;
        map<string, const char*>::iterator itE = mp.end();
        itE--;

        map<string, const char*, greater<string>> mpR(itB, itE);

        for (map<string, const char*>::iterator it = mpR.begin(); it != mpR.end(); it++)
            cout << it->first << " => " << it->second << endl;

        return 0;
    }

The output is:

    plum => purple
    papaya => orange
    blackberry => dark blue-black
    banana => yellow

The first map object has six elements which are:

    apricot => orange
    banana => yellow
    blackberry => dark blue-black
    papaya => orange
    plum => purple
    watermelon => green

The range considered is:

    banana => yellow
    blackberry => dark blue-black
    papaya => orange
    plum => purple
    watermelon => green

In the code, “itB++” points to {“banana”, “yellow”} and “itE–” points to {“watermelon”, “green”} for the range. When handling a range in C++, the final element is not involved in the manipulation. And so the output has four elements with {“watermelon”, “green”} omitted.

The specialization of the Compare template parameter of the second map is greater<string>. If it were less<string> or omitted, the range would have resulted in ascending order.

Comparing two Elements by Key

key_compare key_comp() const

This member function returns a copy of the comparison object used by the map container to compare keys. A comparison object is a function object. It would take two keys as arguments and return true if the left key is less than right. With that, the code segment should be:

        key_compare kc = mp.key_comp();
        bool bl = kc("watermelon", "apricot");

key_compare is not recognized by the compiler. Eliminating key_compare in this code segment, by substituting for kc in the second statement, results in:

        bool bl = mp.key_comp()("watermelon", "apricot");

The following program illustrates the use of key_comp().

    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;
   
    int main()
    {
        map<string, const char*> mp;

        mp["plum"] = "purple";
        mp["blackberry"] = "dark blue-black";
        mp["watermelon"] = "green";
        mp["apricot"] = "orange";
        mp["papaya"] = "orange";
        mp["banana"] = "yellow";

        bool bl = mp.key_comp()("watermelon", "apricot");

        cout << bl << endl;

        return 0;
    }

The output is 0 for false.

The real problem with the above code segment is, that the namespace for key_compare, was not well expressed. If the segment was,

        map<string, const char*>::key_compare kc = mp.key_comp();
        bool bl = kc("watermelon", "apricot");

It would have worked (accepted by the compiler).

value_compare value_comp() const

This member function is similar to key_comp(). Note: here, it is not the value of the key/value pair that is referred to; it is the element of the key/value pair. So, the two arguments for the value_compare function object are iterator elements. The following program uses value_comp(), in comparing the first and last elements, {“apricot”, “orange”} and {“watermelon”, “green”} :

    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;
   
    int main()
    {
        map<string, const char*, less<string>> mp;

        mp["plum"] = "purple";
        mp["blackberry"] = "dark blue-black";
        mp["watermelon"] = "green";
        mp["apricot"] = "orange";
        mp["papaya"] = "orange";
        mp["banana"] = "yellow";

        map<string, const char*>::iterator itB = mp.begin();
        map<string, const char*>::iterator itE = mp.end();
        itE--;

        map<string, const char*>::value_compare vc = mp.value_comp();

        bool bl = vc(*itB, *itE);

        cout << bl << endl;

        return 0;
    }

The output is 1, for true. The iterators itB and itE were dereferenced to have their elements, with the indirection operator.

Sorting of Map Created with Initializer List

In the following program, where sorting is descending, the keys are string objects, instantiated from the string class:

    #include <iostream>
    #include <string>
    #include <map>
    using namespace std;
   
    int main()
    {
        map<string, const char*, greater<string>> mp({{"plum","purple"}, {"blackberry","dark blue-black"}, {"watermelon","green"}, {"apricot","orange"}, {"papaya","orange"}, {"banana","yellow"}});

        for (map<string, const char*>::iterator it = mp.begin(); it != mp.end(); it++)
            cout << it->first << " => " << it->second << endl;

        return 0;
    }

The output is:

    watermelon => green
    plum => purple
    papaya => orange
    blackberry => dark blue-black
    banana => yellow
    apricot => orange

Conclusion

A map is created sorted by keys, ascending. Ascending is the default order. To have it descending, add the template parameter specialization, greater as the third argument, into the template argument list. Note: if the keys are strings, they must be instantiated from the string class, as illustrated above. String keys as const-char* or char-arr[], end up with their pointers sorted and not their literals.

About the author

Chrysanthus Forcha

Discoverer of mathematics Integration from First Principles and related series. Master’s Degree in Technical Education, specializing in Electronics and Computer Software. BSc Electronics. I also have knowledge and experience at the Master’s level in Computing and Telecommunications. Out of 20,000 writers, I was the 37th best writer at devarticles.com. I have been working in these fields for more than 10 years.