Как сделать операцию map::find нечувствительной к регистру?

Поддерживает ли метод map::find поиск без учета регистра? У меня есть следующая карта:

map<string, vector<string> > directory;

и хотите, чтобы поиск ниже игнорировал регистр:

directory.find(search_string);

person Ankur    schedule 26.11.2009    source источник


Ответы (11)


По умолчанию это не так. Вам нужно будет указать пользовательский компаратор в качестве третьего аргумента. Следующий фрагмент поможет вам...

  /************************************************************************/
  /* 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
    }
  };

Используйте его как std::map< std::string, std::vector<std::string>, ci_less > myMap;

ПРИМЕЧАНИЕ: std::lexicographical_compare содержит некоторые важные детали. Сравнение строк не всегда просто, если учитывать локали. См. эту тему на c.l.c++, если интересно.

ОБНОВЛЕНИЕ: в C++11 std::binary_function устарело и не нужно, поскольку типы выводятся автоматически.

  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
    }
  };
person Abhay    schedule 26.11.2009
comment
@Абхай. Спасибо за Ваш ответ. Однако я не совсем уверен, как это на самом деле работает (я относительно новичок в STL). Как определение третьего параметра, который является функцией сравнения или объектом функции, выполняющим нечувствительный к регистру меньше чем, фактически выполняет сравнение без учета регистра. Разве мы не должны использовать оператор == вместо этого. Как это работает на самом деле. Я уверен, что что-то упускаю. - person Ankur; 26.11.2009
comment
@Ankur: std::map обычно реализуется как некая древовидная структура. Метод find() использует функцию сравнения, используемую для сортировки дерева (элемент, который не идет ни до, ни после, считается равным — так называемый строгий слабый порядок) дерева. Это позволяет выполнять поиск за время O(logN) с использованием древовидной структуры карты. Объект-предикат очень похож на функцию сортировки; его оператор() принимает MyMap::value_type& в качестве ссылки и возвращает true, если элемент соответствует вашему критерию поиска. - person Abhay; 26.11.2009
comment
@Ankur: Кроме того, согласно подписи std::map std::map‹Key, Data, Compare, Alloc›, «Сравнить» — это «Строгий слабый порядок», тип аргумента которого — Key. И дерево строится с использованием этого порядка. Чтобы увидеть, что именно представляет собой строгий-слабый порядок с математической точки зрения, прочитайте эту упрощенную рецензию SGI @ sgi.com/tech/stl/StrictWeakOrdering.html - person Abhay; 26.11.2009
comment
Еще один вопрос, почему эти функциональные объекты являются производными от двоичной_функции. Да, это базовый класс для стандартных объектов бинарных функций, но какую цель он решает здесь и вообще? - person Ankur; 26.11.2009
comment
@Ankur: для новичка простой способ запомнить объекты функций: Алгоритмы STL вызывают указатель на функцию с параметрами, которые представляют собой значения, представленные контейнером, в котором работает алгоритм. Они немного универсальнее, чем простые указатели на функции, поскольку они относятся к классу (они могут хранить состояние). Теперь количество параметров, используемых для вызова, зависит от используемого алгоритма. Я думаю, что если вы можете погуглить std::binary_function, вы найдете гораздо более подробное и технически точное объяснение, которое необходимо прочитать, прежде чем играть с STL. - person Abhay; 26.11.2009
comment
Прямо из Мейерса, эффективный STL :) - person Robert S. Barnes; 09.06.2010
comment
Почему функция компаратора const char& существует? Когда это вообще срабатывает? Это деталь реализации STL? - person Victor Sergienko; 22.06.2017
comment
@VictorSergienko: std::lexicographical_compare вызывает его через свой компаратор nocase_compare - person Abhay; 23.06.2017
comment
std::binary_function устарел в C++11 и удален в C++17. - person Andreas H.; 28.07.2017

Вот некоторые другие альтернативы, в том числе одна, которая работает значительно быстрее.

#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;
}
person Robert S. Barnes    schedule 09.06.2010
comment
'? 1: 0 'часть первого метода глупа и не нужна, но в остальном отличный ответ. - person jcoffland; 10.11.2012
comment
К вашему сведению, strcasecmp() не находится в ‹cstring› или ‹string›. Он находится в ‹strings.h›, но я не думаю, что в Windows он есть. - person jcoffland; 10.11.2012
comment
конечно, в тот момент, когда вы говорите is x < 0, у вас теперь есть логическое значение, так почему же вы тогда создаете целое число, которое затем должно быть преобразовано в логическое значение, чтобы соответствовать возвращаемому аргументу? - person Peter Nimmo; 22.10.2013
comment
@StoneFree Да, я не обратил на это внимания. Я думаю, исходя из фона C, я иногда забываю, что есть такая вещь, как фактический тип bool. Я отредактирую это. - person Robert S. Barnes; 22.10.2013
comment
Я думал, что ‹ 0 в strcasecmp возвращается, когда левая часть находится внутри правой части, но строки не совсем равны. Разве вы не хотите == 0? - person ; 08.11.2015

Вы можете создать экземпляр std::map с тремя параметрами: тип ключей, тип значений и функция сравнения -- строгий слабый порядок (по сути, функцию или функтор, ведущий себя как operator< с точки зрения транзитивности и антирефлексивности) по вашему вкусу. Просто определите третий параметр, чтобы сделать «без учета регистра меньше чем» (например, с помощью < в строчных строках, которые он сравнивает), и вы получите желаемую «карту без учета регистра»!

person Alex Martelli    schedule 26.11.2009

Я использую следующее:

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);
person Manuel    schedule 06.01.2010
comment
+1 Для крутости, но вау, некрасиво. Сделайте это функтором, как в моем примере, и определите карту. - person Robert S. Barnes; 10.06.2010

Для С++ 11 и выше:

#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;
}
person James    schedule 15.03.2017

Нет, вы не можете сделать это, используя find, так как в этом случае будет несколько совпадений. Например, при вставке let вы сделали что-то вроде map["A"] = 1 и map["a"] = 2, и теперь, если вы хотите, чтобы регистр не учитывал map.find("a"), каково ожидаемое возвращаемое значение? Самый простой способ решить эту проблему - вставить строку в карту только в одном регистре (либо в верхнем, либо в нижнем регистре), а затем использовать тот же регистр при поиске.

person Naveen    schedule 26.11.2009
comment
-1, заблуждение. Ожидаемое значение для карты без учета регистра будет просто равно 2 (последнее значение, записанное для A==a) . Карты используют строгие слабые порядки, которые (могут) иметь эквивалентные ключи. Любой такой ключ можно использовать взаимозаменяемо. - person MSalters; 26.11.2009
comment
может ввести в заблуждение. Я пытался показать, что если у вас есть карта, чувствительная к регистру, нет возможности иметь функцию find(), которая работает без учета регистра. - person Naveen; 26.11.2009
comment
Справедливое замечание, но этот момент был бы намного яснее. Например. если бы вы объяснили, что std::map поддерживает только один индекс, который может быть либо чувствительным к регистру, либо нечувствительным к регистру, но не обоим. Оттуда это простая ссылка на boost::multi_index, которая поддерживает второй индекс. - person MSalters; 26.11.2009
comment
Если он создает карту с компаратором, нечувствительным к регистру, как предлагает Абхай, то он может использовать версию find для поиска без учета регистра. - person Robert S. Barnes; 09.06.2010
comment
+1, потому что на самом деле другие ответы вводят в заблуждение. Они делают всю карту нечувствительной к регистру, вопрос задается только для того, чтобы сделать map::find() нечувствительным к регистру. - person Andreas Haferburg; 05.10.2012

Если вы не хотите трогать тип карты (чтобы сохранить ее первоначальную простоту и эффективность), но не возражаете против использования более медленной функции поиска без учета регистра (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: Возможно, это была идея Роджера Пейта, но не уверен, так как некоторые детали были немного неверны (std::search?, прямой компаратор строк?)

person Alink    schedule 26.11.2009

Я хотел бы представить краткое решение без использования Boost или шаблонов. Поскольку C++11, вы также можете указать лямбда-выражение в качестве пользовательского компаратора для вашей карты. Для POSIX-совместимой системы решение может выглядеть следующим образом:

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);

Код на Ideone

Для Windows strcasecmp() не существует, но вы можете использовать _stricmp() вместо этого:

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);

Примечание. В зависимости от вашей системы и от того, должны ли вы поддерживать Unicode или нет, вам может понадобиться сравнивать строки по-разному. Вопросы и ответы дают хорошее начало.

person honk    schedule 15.03.2019


Протестировано:

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
person sz9    schedule 27.06.2013

Реализуйте функцию std::less и сравните, изменив оба на один и тот же регистр.

person Vivek    schedule 26.11.2009