Сравнение строк без учета регистра в C++

StackOverflow https://stackoverflow.com/questions/11635

  •  08-06-2019
  •  | 
  •  

Вопрос

Каков наилучший способ сравнения строк без учета регистра в C++ без преобразования строки в верхний или нижний регистр?

Укажите, поддерживают ли эти методы Юникод и насколько они переносимы.

Это было полезно?

Решение

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
}

Другие советы

Воспользуйтесь стандартом char_traits.Напомним, что std::string на самом деле это определение типа для std::basic_string<char>, или, более явно, std::basic_string<char, std::char_traits<char> >char_traits type описывает, как сравниваются персонажи, как они копируются, как они используются и т. д.Все, что вам нужно сделать, это ввести новую строку поверх 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.

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

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

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

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

Обновлять:Бонусная версия C++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);
                      });
}

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

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

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

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

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

Пример:

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

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

Тем не менее, Нормализация Юникода должен быть обязательно прочитан, особенно если вы планируете поддерживать хангыль, тайский и другие азиатские языки.

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

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

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

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


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

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

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

Строковые функции Visual C++, поддерживающие Юникод: http://msdn.microsoft.com/en-us/library/cc194799.aspx

тот, который вы, вероятно, ищете, это _wcsnicmp

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

Вот способ сделать это, хотя он преобразует строки и не поддерживает 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.String В библиотеке есть множество алгоритмов для сравнения без учета регистра и так далее.

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

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

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

Итак, что я придумал, так это:

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, что и следовало ожидать.

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

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

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

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

возвращает истинный если 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

Видеть 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;
}

Демо

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

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

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

Я написал нечувствительную к регистру версию 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;

Сделать это без использования 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 
}

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

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

Просто используйте 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 это одно и то же

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

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

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

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

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

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

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

Опоздал на вечеринку, но вот вариант, в котором используется 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 основанные строки.

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

#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;
}

Если вы не хотите использовать Библиотека повышения тогда вот решение с использованием только стандартного заголовка io С++.

#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;
}

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

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

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

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

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

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

// Case insensitive (could use equivalent _stricmp)  
result = _stricmp( string1, string2 );  

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

std::string s1 = string("Hello");
if ( _stricmp(s1.c_str(), "HELLO") == 0)
   std::cout << "The string are equals.";

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top