Подтвердить что ты не робот

Сравнение строк, нечувствительных к регистру, в С++

Каков наилучший способ сделать нечувствительное к строкам сравнение строк в С++ без преобразования строки во все прописные или все строчные буквы?

Просьба указать, являются ли методы дружественными Unicode и насколько они переносимы.

4b9b3361

Ответ 1

Boost включает в себя удобный алгоритм для этого:

#include <boost/algorithm/string.hpp>
// Or, for fewer header dependencies:
//#include <boost/algorithm/string/predicate.hpp>

std::string str1 = "hello, world!";
std::string str2 = "HELLO, WORLD!";

if (boost::iequals(str1, str2))
{
    // Strings are identical
}

Ответ 2

Воспользуйтесь стандартом char_traits. Напомним, что a std::string на самом деле является typedef для std::basic_string<char> или более явно, std::basic_string<char, std::char_traits<char> >. Тип char_traits описывает, как сопоставляются символы, как они копируются, как они копируются и т.д. Все, что вам нужно сделать, это typedef новую строку поверх basic_string и предоставить ей свой собственный char_traits, который не учитывает регистр без учета регистра.

struct ci_char_traits : public char_traits<char> {
    static bool eq(char c1, char c2) { return toupper(c1) == toupper(c2); }
    static bool ne(char c1, char c2) { return toupper(c1) != toupper(c2); }
    static bool lt(char c1, char c2) { return toupper(c1) <  toupper(c2); }
    static int compare(const char* s1, const char* s2, size_t n) {
        while( n-- != 0 ) {
            if( toupper(*s1) < toupper(*s2) ) return -1;
            if( toupper(*s1) > toupper(*s2) ) return 1;
            ++s1; ++s2;
        }
        return 0;
    }
    static const char* find(const char* s, int n, char a) {
        while( n-- > 0 && toupper(*s) != toupper(a) ) {
            ++s;
        }
        return s;
    }
};

typedef std::basic_string<char, ci_char_traits> ci_string;

Детали находятся на Гуру недели № 29.

Ответ 3

Проблема с повышением заключается в том, что вам нужно связать и зависеть от повышения. Нелегко в некоторых случаях (например, android).

И использование char_traits означает, что все ваши сравнения нечувствительны к регистру, что обычно не является тем, что вы хотите.

Этого должно хватить. Он должен быть достаточно эффективным. Не обрабатывает unicode или что-то еще.

bool iequals(const string& a, const string& b)
{
    unsigned int sz = a.size();
    if (b.size() != sz)
        return false;
    for (unsigned int i = 0; i < sz; ++i)
        if (tolower(a[i]) != tolower(b[i]))
            return false;
    return true;
}

Обновление: версия Bonus С++ 14 (#include <algorithm>):

bool iequals(const string& a, const string& b)
{
    return std::equal(a.begin(), a.end(),
                      b.begin(), b.end(),
                      [](char a, char b) {
                          return tolower(a) == tolower(b);
                      });
}

Ответ 4

Если вы находитесь в системе POSIX, вы можете использовать strcasecmp. Эта функция не является частью стандартного C, хотя и не доступна в Windows. Это приведет к нечувствительности к регистру по сравнению с 8-битными символами, если локаль является POSIX. Если локаль не POSIX, результаты undefined (так что это может сделать локализованное сравнение, или это может быть не так). Широкосимвольный эквивалент недоступен.

В противном случае большое количество исторических реализаций библиотеки C имеют функции stricmp() и strnicmp(). Visual С++ в Windows переименовал их все, префикс их с подчеркиванием, потому что они являются частью стандарта ANSI, поэтому в этой системе они называются _ stricmp или _strnicmp. Некоторые библиотеки могут также иметь широкосимвольные или многобайтовые эквивалентные функции (обычно называемые, например, wcsicmp, mbcsicmp и т.д.).

C и С++ в значительной степени не знают вопросов интернационализации, поэтому нет хорошего решения этой проблемы, за исключением использования сторонней библиотеки. Ознакомьтесь с IBM ICU (Международные компоненты для Unicode), если вам нужна надежная библиотека для C/С++. ICU предназначен как для систем Windows, так и для Unix.

Ответ 5

Вы говорите о нечувствительном сравнении с немым регистром или с полным нормализованным Unicode-сравнением?

Немое сравнение не найдет строки, которые могут быть одинаковыми, но не бинарными.

Пример:

U212B (ANGSTROM SIGN)
U0041 (LATIN CAPITAL LETTER A) + U030A (COMBINING RING ABOVE)
U00C5 (LATIN CAPITAL LETTER A WITH RING ABOVE).

Все эквиваленты, но они также имеют разные двоичные представления.

Тем не менее, Unicode Normalization должно быть обязательно прочитано, особенно если вы планируете поддерживать Hangul, Thaï и другие азиатские языки.

Кроме того, IBM довольно запатентовала наиболее оптимизированные алгоритмы Unicode и сделала их общедоступными. Они также поддерживают реализацию: IBM ICU

Ответ 6

boost:: iequals не совместим с utf-8 в случае строки. Вы можете использовать boost:: locale.

comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
  • Первичный - игнорировать акценты и характерный случай, сравнивая только базовые буквы. Например, "фасад" и "фасад" одинаковы.
  • Вторичный - игнорировать регистр символов, но учитывать акценты. "фасад" и "фасад" разные, но "фасад" и "фасад" одинаковы.
  • Третичный - рассмотрите оба случая и акценты: "Фасад" и "Фасад" различны. Игнорировать пунктуацию.
  • Четвертичный - учитывайте все случаи, акценты и знаки препинания. Слова должны быть одинаковыми в терминах представления Unicode.
  • Идентичный - как четвертичный, но также и кодовый код.

Ответ 7

Моя первая мысль о версии, отличной от юникода, заключалась в том, чтобы сделать что-то вроде этого:


bool caseInsensitiveStringCompare(const string& str1, const string& str2) {
    if (str1.size() != str2.size()) {
        return false;
    }
    for (string::const_iterator c1 = str1.begin(), c2 = str2.begin(); c1 != str1.end(); ++c1, ++c2) {
        if (tolower(*c1) != tolower(*c2)) {
            return false;
        }
    }
    return true;
}

Ответ 8

Вы можете использовать strcasecmp в Unix или stricmp в Windows.

До сих пор не упоминалось, что если вы используете stl-строки с этими методами, полезно сначала сравнить длину двух строк, так как эта информация уже доступна вам в классе строк. Это может предотвратить выполнение дорогостоящего сравнения строк, если две строки, которые вы сравниваете, в первую очередь не совпадают.

Ответ 10

Я пытаюсь собрать хороший ответ со всех сообщений, поэтому помогите мне изменить это:

Вот как это сделать, хотя он и преобразует строки, и не является Unicode дружественным, он должен быть переносимым, что является плюсом:

bool caseInsensitiveStringCompare( const std::string& str1, const std::string& str2 ) {
    std::string str1Cpy( str1 );
    std::string str2Cpy( str2 );
    std::transform( str1Cpy.begin(), str1Cpy.end(), str1Cpy.begin(), ::tolower );
    std::transform( str2Cpy.begin(), str2Cpy.end(), str2Cpy.begin(), ::tolower );
    return ( str1Cpy == str2Cpy );
}

Из того, что я прочитал, это более переносимо, чем stricmp(), потому что stricmp() на самом деле не часть библиотеки std, но реализована только большинством поставщиков компиляторов.

Чтобы получить действительно совместимую с Unicode реализацию, вы должны выйти за пределы библиотеки std. Одна хорошая сторонняя библиотека - это IBM ICU (Международные компоненты для Unicode)

Также boost:: iequals предоставляет довольно хорошую утилиту для такого рода сравнения.

Ответ 11

FYI, strcmp() и stricmp() уязвимы для переполнения буфера, поскольку они просто обрабатываются, пока не нанесут нулевой ограничитель. Безопаснее использовать _strncmp() и _strnicmp().

Ответ 12

В библиотеке Boost.String есть много алгоритмов для проведения сравнений с учетом регистра и т.д.

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

Ответ 13

str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](auto a, auto b){return std::tolower(a)==std::tolower(b);})

Вы можете использовать приведенный выше код на С++ 14, если не можете использовать boost. Вы должны использовать std::towlower для широких символов.

Ответ 14

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

Так что я придумал следующее:

bool icasecmp(const string& l, const string& r)
{
    return l.size() == r.size()
        && equal(l.cbegin(), l.cend(), r.cbegin(),
            [](string::value_type l1, string::value_type r1)
                { return toupper(l1) == toupper(r1); });
}

bool icasecmp(const wstring& l, const wstring& r)
{
    return l.size() == r.size()
        && equal(l.cbegin(), l.cend(), r.cbegin(),
            [](wstring::value_type l1, wstring::value_type r1)
                { return towupper(l1) == towupper(r1); });
}

Простая функция с одной перегрузкой для char и другая для whar_t. Не использует ничего нестандартного, поэтому должно быть хорошо на любой платформе.

Сравнение равенства не будет рассматривать такие проблемы, как кодирование с переменной длиной и нормализацией Unicode, но basic_string не поддерживает то, что я знаю в любом случае, и это обычно не проблема.

В случаях, когда требуется более сложное лексикографическое манипулирование текстом, вам просто нужно использовать стороннюю библиотеку, такую ​​как Boost, что и следовало ожидать.

Ответ 15

См. std::lexicographical_compare:

// lexicographical_compare example
#include <iostream>  // std::cout, std::boolalpha
#include <algorithm>  // std::lexicographical_compare
#include <cctype>  // std::tolower

// a case-insensitive comparison function:
bool mycomp (char c1, char c2) {
    return std::tolower(c1)<std::tolower(c2);
}

int main () {
    char foo[] = "Apple";
    char bar[] = "apartment";

    std::cout << std::boolalpha;

    std::cout << "Comparing foo and bar lexicographically (foo < bar):\n";

    std::cout << "Using default comparison (operator<): ";
    std::cout << std::lexicographical_compare(foo, foo + 5, bar, bar + 9);
    std::cout << '\n';

    std::cout << "Using mycomp as comparison object: ";
    std::cout << std::lexicographical_compare(foo, foo + 5, bar, bar + 9, mycomp);
    std::cout << '\n';

    return 0;
}

Демо

Ответ 16

Короткий и приятный. Никаких других зависимостей, чем расширенный std C lib.

strcasecmp(str1.c_str(), str2.c_str()) == 0

возвращает true, если str1 и str2 равны. strcasecmp может не существовать, могут быть аналоги stricmp, strcmpi и т.д.

Пример кода:

#include <iostream>
#include <string>
#include <string.h> //For strcasecmp(). Also could be found in <mem.h>

using namespace std;

/// Simple wrapper
inline bool str_ignoreCase_cmp(std::string const& s1, std::string const& s2) {
    if(s1.length() != s2.length())
        return false;  // optimization since std::string holds length in variable.
    return strcasecmp(s1.c_str(), s2.c_str()) == 0;
}

/// Function object - comparator
struct StringCaseInsensetiveCompare {
    bool operator()(std::string const& s1, std::string const& s2) {
        if(s1.length() != s2.length())
            return false;  // optimization since std::string holds length in variable.
        return strcasecmp(s1.c_str(), s2.c_str()) == 0;
    }
    bool operator()(const char *s1, const char * s2){ 
        return strcasecmp(s1,s2)==0;
    }
};


/// Convert bool to string
inline char const* bool2str(bool b){ return b?"true":"false"; }

int main()
{
    cout<< bool2str(strcasecmp("asd","AsD")==0) <<endl;
    cout<< bool2str(strcasecmp(string{"aasd"}.c_str(),string{"AasD"}.c_str())==0) <<endl;
    StringCaseInsensetiveCompare cmp;
    cout<< bool2str(cmp("A","a")) <<endl;
    cout<< bool2str(cmp(string{"Aaaa"},string{"aaaA"})) <<endl;
    cout<< bool2str(str_ignoreCase_cmp(string{"Aaaa"},string{"aaaA"})) <<endl;
    return 0;
}

Вывод:

true
true
true
true
true

Ответ 17

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

Лучшим подходом для этого преобразования является сделать это до сравнения. Это позволяет вам проявлять большую гибкость, когда речь идет о схемах кодирования, о которых должен знать ваш фактический оператор сравнения.

Вы можете, конечно, "скрыть" это преобразование за вашей собственной строковой функцией или классом, но вам все равно нужно преобразовать строки перед сравнением.

Ответ 18

Я написал нечувствительную к регистру версию char_traits для использования с std:: basic_string для генерации std::string, которая не учитывает регистр при выполнении сравнений, поиска и т.д., используя встроенный элемент std:: basic_string функции.

Итак, другими словами, я хотел сделать что-то вроде этого.

std::string a = "Hello, World!";
std::string b = "hello, world!";

assert( a == b );

... который std::string не может обрабатывать. Здесь использование моих новых char_traits:

std::istring a = "Hello, World!";
std::istring b = "hello, world!";

assert( a == b );

... и здесь реализация:

/*  ---

        Case-Insensitive char_traits for std::string's

        Use:

            To declare a std::string which preserves case but ignores case in comparisons & search,
            use the following syntax:

                std::basic_string<char, char_traits_nocase<char> > noCaseString;

            A typedef is declared below which simplifies this use for chars:

                typedef std::basic_string<char, char_traits_nocase<char> > istring;

    --- */

    template<class C>
    struct char_traits_nocase : public std::char_traits<C>
    {
        static bool eq( const C& c1, const C& c2 )
        { 
            return ::toupper(c1) == ::toupper(c2); 
        }

        static bool lt( const C& c1, const C& c2 )
        { 
            return ::toupper(c1) < ::toupper(c2);
        }

        static int compare( const C* s1, const C* s2, size_t N )
        {
            return _strnicmp(s1, s2, N);
        }

        static const char* find( const C* s, size_t N, const C& a )
        {
            for( size_t i=0 ; i<N ; ++i )
            {
                if( ::toupper(s[i]) == ::toupper(a) ) 
                    return s+i ;
            }
            return 0 ;
        }

        static bool eq_int_type( const int_type& c1, const int_type& c2 )
        { 
            return ::toupper(c1) == ::toupper(c2) ; 
        }       
    };

    template<>
    struct char_traits_nocase<wchar_t> : public std::char_traits<wchar_t>
    {
        static bool eq( const wchar_t& c1, const wchar_t& c2 )
        { 
            return ::towupper(c1) == ::towupper(c2); 
        }

        static bool lt( const wchar_t& c1, const wchar_t& c2 )
        { 
            return ::towupper(c1) < ::towupper(c2);
        }

        static int compare( const wchar_t* s1, const wchar_t* s2, size_t N )
        {
            return _wcsnicmp(s1, s2, N);
        }

        static const wchar_t* find( const wchar_t* s, size_t N, const wchar_t& a )
        {
            for( size_t i=0 ; i<N ; ++i )
            {
                if( ::towupper(s[i]) == ::towupper(a) ) 
                    return s+i ;
            }
            return 0 ;
        }

        static bool eq_int_type( const int_type& c1, const int_type& c2 )
        { 
            return ::towupper(c1) == ::towupper(c2) ; 
        }       
    };

    typedef std::basic_string<char, char_traits_nocase<char> > istring;
    typedef std::basic_string<wchar_t, char_traits_nocase<wchar_t> > iwstring;

Ответ 19

Выполнение этого без использования Boost можно сделать, получив указатель строки C с помощью c_str() и используя strcasecmp:

std::string str1 ="aBcD";
std::string str2 = "AbCd";;
if (strcasecmp(str1.c_str(), str2.c_str()) == 0)
{
    //case insensitive equal 
}

Ответ 20

У меня был хороший опыт использования Международные компоненты для Unicode-библиотек - они чрезвычайно мощные и предоставляют методы для преобразования, локали поддержка, рендеринг даты и времени, отображение случаев (что вам кажется не нужно) и collation, который включает в себя case- и accent- нечувствительное сравнение (и многое другое). Я использовал только библиотеки С++, но они, похоже, также имеют версию Java.

Существуют методы для выполнения нормализованных сравнений, упомянутых @Coincoin, и могут даже учитывать локаль - например (и это пример сортировки, а не строгое равенство), традиционно на испанском (в Испании), комбинация букв "ll" сортирует между "l" и "m", поэтому "lz" < "ll" "Ма".

Ответ 21

Просто используйте strcmp() для чувствительности к регистру и strcmpi() или stricmp() для сравнения без учета регистра. Которые находятся в файле заголовка <string.h>

формат:

int strcmp(const char*,const char*);    //for case sensitive
int strcmpi(const char*,const char*);   //for case insensitive

Применение:

string a="apple",b="ApPlE",c="ball";
if(strcmpi(a.c_str(),b.c_str())==0)      //(if it is a match it will return 0)
    cout<<a<<" and "<<b<<" are the same"<<"\n";
if(strcmpi(a.c_str(),b.c_str()<0)
    cout<<a[0]<<" comes before ball "<<b[0]<<", so "<<a<<" comes before "<<b;

Выход

apple и ApPlE одинаковы

a приходит до b, поэтому яблоко появляется перед шаром

Ответ 22

Просто обратите внимание на любой метод, который вы, наконец, выберете, если этот метод включает использование strcmp, о котором говорят некоторые ответы:

strcmp не работает с данными Unicode в целом. В общем, он даже не работает с кодировками Unicode на основе байтов, такими как utf-8, поскольку strcmp выполняет только сравнения байтов за байты, а кодовые точки Unicode, закодированные в utf-8, могут принимать более 1 байт. Единственный конкретный случай Unicode strcmp, который правильно обрабатывается, - это когда строка, закодированная с использованием кодировки на основе байта, содержит только кодовые точки ниже U + 00FF, тогда сравнение байтов за байтом достаточно.

Ответ 23

По состоянию на начало 2013 года проект ICU, поддерживаемый IBM, является довольно хорошим ответом на это.

http://site.icu-project.org/

ICU - это "полная переносимая библиотека Unicode, которая точно отслеживает отраслевые стандарты". Для конкретной проблемы сравнения строк объект Collation делает то, что вы хотите.

Проект Mozilla принял ICU для интернационализации в Firefox в середине 2012 года; вы можете отслеживать инженерное обсуждение, в том числе вопросы построения систем и размер файла данных, здесь:

Ответ 24

Поздно к партии, но вот вариант, который использует std::locale, и, таким образом, правильно обрабатывает турецкий язык:

auto tolower = std::bind1st(
    std::mem_fun(
        &std::ctype<char>::tolower),
    &std::use_facet<std::ctype<char> >(
        std::locale()));

дает вам функтор, который использует активный язык для преобразования символов в нижний регистр, который затем можно использовать с помощью std::transform для генерации строчных строк:

std::string left = "fOo";
transform(left.begin(), left.end(), left.begin(), tolower);

Это также работает для строк wchar_t.

Ответ 25

Похоже, что вышеприведенные решения не используют метод сравнения и не реализуют итоговые значения снова, поэтому вот мое решение и надеюсь, что он работает для вас (он отлично работает).

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
string tolow(string a)
{
    for(unsigned int i=0;i<a.length();i++)
    {
        a[i]=tolower(a[i]);
    }
    return a;
}
int main()
{
    string str1,str2;
    cin>>str1>>str2;
    int temp=tolow(str1).compare(tolow(str2));
    if(temp>0)
        cout<<1;
    else if(temp==0)
        cout<<0;
    else
        cout<<-1;
}

Ответ 26

Если вы не хотите использовать библиотеку Boost, вот решение этой проблемы с использованием только стандартного заголовка io C++.

#include <iostream>

struct iequal
{
    bool operator()(int c1, int c2) const
    {
        // case insensitive comparison of two characters.
        return std::toupper(c1) == std::toupper(c2);
    }
};

bool iequals(const std::string& str1, const std::string& str2)
{
    // use std::equal() to compare range of characters using the functor above.
    return std::equal(str1.begin(), str1.end(), str2.begin(), iequal());
}

int main(void)
{
    std::string str_1 = "HELLO";
    std::string str_2 = "hello";

    if(iequals(str_1,str_2))
    {
        std::cout<<"String are equal"<<std::endl;   
    }

    else
    {
        std::cout<<"String are not equal"<<std::endl;
    }


    return 0;
}

Ответ 27

Если у вас есть вектор строк, например:

std::sort(std::begin(myvector), std::end(myvector), [](std::string const &a, std::string const &b)
{
    return std::lexicographical_compare(std::begin(a), std::end(a), std::begin(b), std::end(b), [](std::string::value_type a, std::string::value_type b)
    {
        return std::tolower(a) < std::tolower(b); //case-insensitive
    });
});

http://ideone.com/N6sq6X

Ответ 28

Если вам нужно чаще сравнивать исходную строку с другими строками, одним из элегантных решений является использование регулярного выражения.

std::wstring first = L"Test";
std::wstring second = L"TEST";

std::wregex pattern(first, std::wregex::icase);
bool isEqual = std::regex_match(second, pattern);

Ответ 29

Простым способом сравнения двух строк в С++ (проверенных для Windows) является использование _stricmp

 //Нечувствительность к регистру (может использовать эквивалентный _stricmp)
result = _stricmp (строка1, строка2);
Код>

Если вы хотите использовать с std::string, пример:

  std:: string s1 = string ( "Hello" );
if (_stricmp (s1.c_str(), "HELLO" ) == 0)  std:: cout < "Строка равна.";
Код>

Для получения дополнительной информации: https://msdn.microsoft.com/он-он/library/e0z9k731.aspx

Ответ 30

bool insensitive_c_compare(char A, char B){
  static char mid_c = ('Z' + 'a') / 2 + 'Z';
  static char up2lo = 'A' - 'a'; /// the offset between upper and lowers

  if ('a' >= A and A >= 'z' or 'A' >= A and 'Z' >= A)
      if ('a' >= B and B >= 'z' or 'A' >= B and 'Z' >= B)
      /// check that the character is infact a letter
      /// (trying to turn a 3 into an E would not be pretty!)
      {
        if (A > mid_c and B > mid_c or A < mid_c and B < mid_c)
        {
          return A == B;
        }
        else
        {
          if (A > mid_c)
            A = A - 'a' + 'A'; 
          if (B > mid_c)/// convert all uppercase letters to a lowercase ones
            B = B - 'a' + 'A';
          /// this could be changed to B = B + up2lo;
          return A == B;
        }
      }
}

это, вероятно, можно было бы сделать гораздо более эффективным, но вот громоздкая версия со всеми ее битами.

не все, что переносится, но хорошо работает со всем, что есть на моем компьютере (не знаю, я из изображений не слов)