51

Does the map::find method support case insensitive search? I have a map as follows:

map<string, vector<string> > directory;

and want the below search to ignore case:

directory.find(search_string);
honk
  • 9,137
  • 11
  • 75
  • 83
Ankur
  • 11,239
  • 22
  • 63
  • 66

12 Answers12

72

It does not by default. You will have to provide a custom comparator as a third argument. Following snippet will help you...

  /************************************************************************/
  /* Comparator for case-insensitive comparison in STL assos. containers  */
  /************************************************************************/
  struct ci_less : std::binary_function<std::string, std::string, bool>
  {
    // case-independent (ci) compare_less binary function
    struct nocase_compare : public std::binary_function<unsigned char,unsigned char,bool> 
    {
      bool operator() (const unsigned char& c1, const unsigned char& c2) const {
          return tolower (c1) < tolower (c2); 
      }
    };
    bool operator() (const std::string & s1, const std::string & s2) const {
      return std::lexicographical_compare 
        (s1.begin (), s1.end (),   // source range
        s2.begin (), s2.end (),   // dest range
        nocase_compare ());  // comparison
    }
  };

Use it like std::map< std::string, std::vector<std::string>, ci_less > myMap;

NOTE: std::lexicographical_compare has some nitty-gritty details. String comparison isn't always straightforward if you consider locales. See this thread on c.l.c++ if interested.

UPDATE: With C++11 std::binary_function is deprecated and is unnecessary as the types are deduced automatically.

  struct ci_less
  {
    // case-independent (ci) compare_less binary function
    struct nocase_compare
    {
      bool operator() (const unsigned char& c1, const unsigned char& c2) const {
          return tolower (c1) < tolower (c2); 
      }
    };
    bool operator() (const std::string & s1, const std::string & s2) const {
      return std::lexicographical_compare 
        (s1.begin (), s1.end (),   // source range
        s2.begin (), s2.end (),   // dest range
        nocase_compare ());  // comparison
    }
  };
Abhay
  • 7,092
  • 3
  • 36
  • 50
  • @Abhay. Thanks for your answer. However, I'm not entirely sure how does this actually work (being relatively new to STL). How does defining a third parameter, which being a comparison function or function object doing "case-insensitive" less-than, actually does case-insensitive comparison. Shouldn't we be using == operator instead. How does this actually work. I'm sure I'm mising something. – Ankur Nov 26 '09 at 07:56
  • 2
    @Ankur: std::map is usually implemented as some sort of tree structure. The find() method uses the comparison function that is used for sorting ( an item that comes neither before nor after is considered equal -- a.k.a strict weak ordering) the tree. This allows the search to be performed in O(logN) time using the map's tree structure. The predicate object is very much like a sort function; its operator() takes a MyMap::value_type& as a reference, and return true if the item matches your search criterion. – Abhay Nov 26 '09 at 08:06
  • @Ankur: Also, as per std::map's signature std::map, 'Compare' is a 'Strict Weak Ordering' whose argument type is Key. And the tree is built using this ordering. To see what exactly is strict-weak-ordering in mathematical terms, read this simplified SGI write-up @ http://www.sgi.com/tech/stl/StrictWeakOrdering.html – Abhay Nov 26 '09 at 08:20
  • 1
    One more question, why do derive these function objects from binary_function. Yes its a base class for standard binary function objects, but what purpose does it solve here and in general? – Ankur Nov 26 '09 at 16:03
  • @Ankur: For a beginner, an easy way to remember the function objects is "The STL algorithms call a function-pointer with parameters which are values represented by the container on which the algorithm runs". They are a little versatile than plain function-pointers as they a class-type(they can store state). Now the number of parameters used to call depends on the algorithm used. I think if you can google on std::binary_function, u will find a much more detailed and technically accurate explanation which is a must read before playing with STL. – Abhay Nov 26 '09 at 16:17
  • Straight out of Meyers, "Effective STL" :) – Robert S. Barnes Jun 09 '10 at 17:09
  • Why is `const char&` comparator function there? When does it ever trigger? Is it a STL implementation detail? – Victor Sergienko Jun 21 '17 at 21:12
  • 1
    @VictorSergienko: std::lexicographical_compare calls it through its comparator nocase_compare – Abhay Jun 22 '17 at 23:43
  • 1
    `std::binary_function` is deprecated in C++11 and removed in C++17. – Andreas H. Jul 28 '17 at 12:24
23

Here are some other alternatives, including one which performs significantly faster.

#include    <map>
#include    <string>
#include    <cstring>
#include    <iostream>
#include    <boost/algorithm/string.hpp>

using std::string;
using std::map;
using std::cout;
using std::endl;

using namespace boost::algorithm;

// recommended in Meyers, Effective STL when internationalization and embedded
// NULLs aren't an issue.  Much faster than the STL or Boost lex versions.
struct ciLessLibC : public std::binary_function<string, string, bool> {
    bool operator()(const string &lhs, const string &rhs) const {
        return strcasecmp(lhs.c_str(), rhs.c_str()) < 0 ;
    }
};

// Modification of Manuel's answer
struct ciLessBoost : std::binary_function<std::string, std::string, bool>
{
    bool operator() (const std::string & s1, const std::string & s2) const {
        return lexicographical_compare(s1, s2, is_iless());
    }
};

typedef map< string, int, ciLessLibC> mapLibc_t;
typedef map< string, int, ciLessBoost> mapBoost_t;

int main(void) {
    mapBoost_t cisMap; // change to test other comparitor 

    cisMap["foo"] = 1;
    cisMap["FOO"] = 2;

    cisMap["bar"] = 3;
    cisMap["BAR"] = 4;

    cisMap["baz"] = 5;
    cisMap["BAZ"] = 6;

    cout << "foo == " << cisMap["foo"] << endl;
    cout << "bar == " << cisMap["bar"] << endl;
    cout << "baz == " << cisMap["baz"] << endl;

    return 0;
}
Robert S. Barnes
  • 39,711
  • 30
  • 131
  • 179
  • 2
    The '? 1 : 0' part in the first method is silly and unnecessary but otherwise great answer. – jcoffland Nov 10 '12 at 05:50
  • 1
    FYI, strcasecmp() is not in or . It is in but I don't think Windows has it. – jcoffland Nov 10 '12 at 06:05
  • 1
    surely the moment you say `is x < 0`, you've now got a boolean, so why are you then producing an integer which will then have to get converted to bool to match the return argument? – Peter Nimmo Oct 22 '13 at 14:19
  • @StoneFree Right, I didn't pay attention to that. I guess coming from a C background I sometimes forget that there is such a thing as an actual `bool` type. I'll edit it. – Robert S. Barnes Oct 22 '13 at 15:03
  • I thought < 0 on strcasecmp is returned when the left hand side is found inside the right hand side, but the strings are not exactly equal. Wouldn't you want == 0? –  Nov 08 '15 at 15:10
  • @user562566 relative order is not equality. `std::set` requires [strict weak order](https://www.boost.org/sgi/stl/StrictWeakOrdering.html) – sehe Mar 03 '22 at 16:56
10

For C++11 and beyond:

#include <strings.h>
#include <map>
#include <string>

namespace detail
{

struct CaseInsensitiveComparator
{
    bool operator()(const std::string& a, const std::string& b) const noexcept
    {
        return ::strcasecmp(a.c_str(), b.c_str()) < 0;
    }
};

}   // namespace detail


template <typename T>
using CaseInsensitiveMap = std::map<std::string, T, detail::CaseInsensitiveComparator>;



int main(int argc, char* argv[])
{
    CaseInsensitiveMap<int> m;

    m["one"] = 1;
    std::cout << m.at("ONE") << "\n";

    return 0;
}
James
  • 9,064
  • 3
  • 31
  • 49
6

You can instantiate std::map with three parameters: type of keys, type of values, and comparison function -- a strict weak ordering (essentially, a function or functor behaving like operator< in terms of transitivity and anti-reflexivity) of your liking. Just define the third parameter to do "case-insensitive less-than" (e.g. by a < on the lowercased strings it's comparing) and you'll have the "case-insensitive map" you desire!

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
6

I use the following:

bool str_iless(std::string const & a, 
               std::string const & b)
{
    return boost::algorithm::lexicographical_compare(a, b,  
                                                     boost::is_iless());
}
std::map<std::string, std::string, 
         boost::function<bool(std::string const &, 
                              std::string const &)> 
         > case_insensitive_map(&str_iless);
Manuel
  • 12,749
  • 1
  • 27
  • 35
5

In case you don't want to touch the map type (to keep it's original simplicity and efficiency), but don't mind using a slower case-insensitive find function (O(N)):

string to_lower(string s) {
    transform(s.begin(), s.end(), s.begin(), (int(*)(int)) tolower );
    return s;
}

typedef map<string, int> map_type;

struct key_lcase_equal {
    string lcs;
    key_lcase_equal(const string& s) : lcs(to_lower(s)) {}
    bool operator()(const map_type::value_type& p) const {
        return to_lower(p.first) == lcs;
    }
};

map_type::iterator find_ignore_case(map_type& m, const string& s) {
    return find_if(m.begin(), m.end(), key_lcase_equal(s));
}

PS: Maybe it was Roger Pate's idea, but not sure, since some details were a bit off (std::search?, direct string comparator?)

Alink
  • 394
  • 2
  • 4
4

No, you can not do that using find as in that case there will be multiple matches. For example, while inserting lets you have done something like map["A"] = 1 and map["a"] = 2 and now if you want a case insensitive map.find("a") what is the expected return value? The simplest way to solve this would be insert the string into map in only one case (either upper or lower case) and then using the same case while doing the find.

Naveen
  • 74,600
  • 47
  • 176
  • 233
  • -1, misleading. The expected value for a case-insensitive map would simply be 2 (last value written for A"=="a") . Maps use strict weak orderings, which (can) have equivalent keys. Any such key can be used interchangably. – MSalters Nov 26 '09 at 09:44
  • 1
    may be misleading..what I was trying to show was that if you have a case sensitive map there is no way to have a find() function which works in case insensitive manner. – Naveen Nov 26 '09 at 09:54
  • Fair point, but that point would have been a lot clearer. E.g. if you had explained that `std::map` supports but a single index, which can be either case-sensitive of case-insensitive but not both. From there it's an easy link to `boost::multi_index`, which does support a second index. – MSalters Nov 26 '09 at 13:41
  • If he creates the map with a case insensitive comparator as Abhay suggests then he can use the member version of find for case insensitive find. – Robert S. Barnes Jun 09 '10 at 17:08
  • +1, because actually the other answers are misleading. They make the entire map case-insensitive, the question only asked for making map::find() case-insensitive. – Andreas Haferburg Oct 05 '12 at 11:14
2

I'd like to present a short solution without using Boost or templates. Since C++11 you can also provide a lambda expression as custom comparator to your map. For a POSIX-compatible system, the solution could look as follows:

auto comp = [](const std::string& s1, const std::string& s2) {
    return strcasecmp(s1.c_str(), s2.c_str()) < 0;
};
std::map<std::string, std::vector<std::string>, decltype(comp)> directory(comp);

Code on Ideone

For Window, strcasecmp() does not exist, but you can use _stricmp() instead:

auto comp = [](const std::string& s1, const std::string& s2) {
    return _stricmp(s1.c_str(), s2.c_str()) < 0;
};
std::map<std::string, std::vector<std::string>, decltype(comp)> directory(comp);

Note: Depending on your system and whether you have to support Unicode or not, you might need to compare strings in a different way. This Q&A gives a good start.

honk
  • 9,137
  • 11
  • 75
  • 83
1

Tested:

template<typename T>
struct ci_less:std::binary_function<T,T,bool>
  { bool operator() (const T& s1,const T& s2) const { return boost::ilexicographical_compare(s1,s2); }};

...

map<string,int,ci_less<string>> x=boost::assign::map_list_of
        ("One",1)
        ("Two",2)
        ("Three",3);

cout << x["one"] << x["TWO"] <<x["thrEE"] << endl;

//Output: 123
sz9
  • 95
  • 8
1

The Compare element of the map template defaults to a binary comparison class "less". Look at the implementation:

http://www.cplusplus.com/reference/std/functional/less/

You can likely create your own class that derives from binary_function (the parent class to less) and do the same comparison without case sensitivity.

0

Implement std::less function and compare by changing both to same case.

Vivek
  • 473
  • 1
  • 3
  • 10
0

This is the cross-platform standard c++ solution unlike strcasecmp (which is only available for posix), without using any external libraries like boost, that I have personally written. It takes advantage of the comparison function of std::map.

#include <algorithm>                                                            
#include <cctype>                                                               
#include <iostream>                                                             
#include <map>                                                                  
#include <string>                                                               
                                                                                
bool caseInsensitiveCompare(const std::string& a, const std::string& b) {       
  std::string aLower = a;                                                       
  std::string bLower = b;                                                       
  std::transform(aLower.begin(), aLower.end(), aLower.begin(), [](unsigned char c){ return std::tolower(c); });
  std::transform(bLower.begin(), bLower.end(), bLower.begin(), [](unsigned char c){ return std::tolower(c); });
  return aLower < bLower;                                                       
};                                                                              
                                                                                
int main()                                                                      
{                                                                               
  std::map<std::string, std::string, decltype(&caseInsensitiveCompare)> myMap(&caseInsensitiveCompare);
  myMap.insert({"foo", "foo"});                                                 
  myMap.insert({"bar", "bar"});                                                 
  myMap.insert({"baz", "baz"});                                                 
                                                                                
  auto it = myMap.find("FoO");                                                  
  if (it != myMap.end()) std::cout << "Found FoO: " << it->second << std::endl;      
  else std::cout << "Not found FoO" << std::endl;                              
                                                                                
  it = myMap.find("foo");                                                       
  if (it != myMap.end()) std::cout << "Found foo: " << it->second << std::endl;    
  else std::cout << "Not found foo" << std::endl;                            
                                                                                
  it = myMap.find("not contained");                                                       
  if (it != myMap.end()) std::cout << "Found not contained: " << it->second << std::endl;    
  else std::cout << "Not found notcontained" << std::endl;                            
                                                                                                                                                                          
  return 0;                                                                     
}
László Papp
  • 51,870
  • 39
  • 111
  • 135