C++

C++ map Comparator

A map is a list structure of elements. Each element is a key/value pair, and the keys are unique. A map can be sorted by keys from smallest to biggest. In a sort, a key moves with its corresponding value. Sorting in ascending order like this uses the map comparator of the form, less-than ().

The following code sorts a map by keys in ascending order, during creation of the map:

        map<char, int, less<char>> mp = {{'P',1}, {'N',2}, {'Q',3}, {'M',4}, {'O',5}};

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

The output is:

    M => 4
    N => 2
    O => 5
    P => 1
    Q => 3

The actual code for the comparator here, is “less<char>”, in the map template specialization. The following code sorts a map by keys in descending order, during creation of the map:

        map<char, int, greater<char>> mp = {{'P',1}, {'N',2}, {'Q',3}, {'M',4}, {'O',5}};

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

The output is:

    Q => 3
    P => 1
    O => 5
    N => 2
    M => 4

The actual code for the comparator here is “greater<char>” in the map template specialization.

For the above code samples, the map library is included in the program. The default comparator form is less. So, it could have been omitted in the previous code to have,

        map<char, int> mp

for the map template specialization.

If the programmer is not satisfied with the comparator offered by C++, the programmer can write his own. This article explains how the programmer can apply his own comparator to the map. Normal comparison of string keys is case-sensitive. The example of case-insensitive comparison is used.

Article Content

Normal Comparison of String Keys

Consider a class of five students, for a map of first names against marks, in a test, as follows:

    Mary => 70
    John => 60
    Susan => 50
    Paul => 45
    Joseph => 75

A C++ map for the default comparator, less is in the program:

    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;

    int main()
    {
        map<string, int> mp = {{"Mary",70}, {"John",60}, {"Susan",50}, {"Paul",45}, {"Joseph",75}};

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

        return 0;
    }

The output is:

    John => 60
    Joseph => 75
    Mary => 70
    Paul => 45
    Susan => 50

The comparison is case-sensitive. The program begins with the inclusion of the iostream library for the cout object. Next, the map library is included; and then the string library is also included. The statement that follows ensures that any name used in the program is from the standard namespace unless otherwise indicated.

In the main() function, the map is declared with initialization. The next code segment in the main() function displays the sorted result (by key).

Custom Case Insensitive Comparison Operator

The less-than (<) operator of the string class of the C++ standard library is case-sensitive. If the programmer needs a case insensitive string literal less-than operator, he has to right his own operator function. An operator is actually a function that begins in a special way.

From the user’s point of view, the less-than operation is:

    left < right

where left and right are operands. This operation yields true if the left operand is less than the right operand. If it happens that the left operand is the same as the right operand or is greater, it yields false. This operator is used for sorting in ascending order.

A custom case insensitive less-than comparison operator, for the string literal, is:

        bool operator()(char const* left, char const* right) const {
            for ( ; *left != '\0' && *right != '\0'; ++left, ++right ) {
                if ( tolower(*left) != tolower(*right) )
                {
                   return ( tolower(*left) < tolower(*right) );
                }
                else if ( *left != *right)
                {
                   if ( *(left+1) == '\0' && *(right+1) == '\0' )
                   {
                      return (*left < *right);
                   }
                }
             }
             return (tolower(*left) < tolower(*right));
        }

Here, the symbol for the operator is not <; it is (), which is coded to mean <. The arguments, left and right, are for the left and right operands, respectively. “char const*” means the content characters cannot be changed. “const” just after the parameter list, means the key value (string) referenced, in the map, cannot be changed, relative to the key/value pair. This does not mean that the order of each key/value pair element in the map cannot be changed.

Despite the fact that the targeted less-than operator has been defined as (), < is still used within the code.

The for-loop compares the left and right string literals, character-by-character, beginning from the left-most-character. It checks if the first characters of each operand are the same when both are in lowercase (case insensitive). If they are not the same, then true is returned if the left character is less than the right character; otherwise, false is returned; and the operator function stops iteration because the answer has been gotten. If they are the same, then iteration continues with the second corresponding pair of characters.

Before comparison continues to the second corresponding pair of characters, the code checks if the corresponding characters were the same but of different cases; and if the string literals were of the same length and have reached their ends. If all these are true, then the ”, can be written for descending order sorting, but that will not be addressed in this article.

Comparison Template Specialization

In simple terms, the template parameter list for the map is:

    template<class Key, class T, class Compare = less<Key>>

Notice the default comparator of less<Key>. For the above map, mp, and for the case insensitive comparison, the specialization would be:

    map<const char*, int, cicomp>

where cicomp is the comparator, and it is a type. This type is the name of a struct or the name of a class. The struct or class typically has just one member, which is the above operator function definition. In this specialization, “const char*” has been used instead of the string type.

With the struct type, members not preceded with the specifier, “public:” are by default, public. With the class type, the above operator function has to be a public member. So the struct type definition, would be:

    struct cicomp {
        bool operator()(char const* left, char const* right) const {
            for ( ; *left != '\0' && *right != '\0'; ++left, ++right ) {
                if ( tolower(*left) != tolower(*right) )
                {
                   return ( tolower(*left) < tolower(*right) );
                }
                else if ( *left != *right)
                {
                   if ( *(left+1) == '\0' && *(right+1) == '\0' )
                   {
                      return (*left < *right);
                   }
                }
             }
             return (tolower(*left) < tolower(*right));
        }
    };

The main difference between the two definitions is the use of the specifier, “public:”. The class type definition would be:

    class cicomp {
        public:
        bool operator()(char const* left, char const* right) const {
            for ( ; *left != '\0' && *right != '\0'; ++left, ++right ) {
                if ( tolower(*left) != tolower(*right) )
                {
                   return ( tolower(*left) < tolower(*right) );
                }
                else if ( *left != *right)
                {
                   if ( *(left+1) == '\0' && *(right+1) == '\0' )
                   {
                      return (*left < *right);
                   }
                }
             }
             return (tolower(*left) < tolower(*right));
        }
    };

The program for this comparator should begin with:

    #include <&iostreamgt;
    #include <&mapgt;
    #include <&cctypegt;
    using namespace std;

The cctype library is for the tolower() function.

Main Function for Custom Comparator

The following C++ main() function is for either the struct type or class type:

    int main()
    {
        map<const char*, int, cicomp> mp = {{"Mary",70}, {"John",60}, {"Susan",50}, {"Paul",45}, {"Joseph",75}};
 
        for (map<const char*, int, cicomp>::iterator ite = mp.begin(); ite != mp.end(); ite++)
            cout << ite->first << " => " << ite->second << endl;

        return 0;
    }

The output is,

    John => 60
    Joseph => 75
    Mary => 70
    Paul => 45
    Susan => 50

for the case insensitive string literal comparator. Note that the template specialization for the iterator is the same as the template specialization for the map.

Conclusion

The default comparator for map sorting is less. Comparator is a type whose name is the third argument of the map declaration, of the map template specialization. This type can be a struct or a class, whose possibly only member, is a custom operator definition. This article has shown the case for a custom, less-than operator. Other comparison operators can similarly be defined.

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.