writing Comparação de cadeias sem distinção entre maiúsculas e minúsculas em C++




writing strings in c++ (24)

Qual é a melhor maneira de fazer a comparação de cadeias sem distinção entre maiúsculas e minúsculas em C ++ sem transformar uma cadeia de caracteres em maiúsculas ou minúsculas?

Por favor, indique se os métodos são compatíveis com Unicode e quão portáteis eles são.


Meu primeiro pensamento para uma versão não unicode foi fazer algo assim:


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

Curto e legal. Nenhuma outra dependência, do que std C lib.

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

retorna true se str1 e str2 forem iguais. strcasecmp pode não existir, pode haver stricmp analógicos, strcmpi , etc.

Exemplo de código:

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

Saída:

true
true
true
true
true

Eu tive uma boa experiência usando os componentes internacionais para bibliotecas Unicode - eles são extremamente poderosos e fornecem métodos para conversão, suporte a localidade, renderização de data e hora, mapeamento de caso (que você não parece querer) e collation , que inclui comparação insensível a maiúsculas e minúsculas (e mais). Eu usei apenas a versão C ++ das bibliotecas, mas elas também parecem ter uma versão Java.

Existem métodos para realizar comparações normalizadas como referido por @Coincoin, e pode até mesmo considerar o código de idioma - por exemplo (e este um exemplo de classificação, não estritamente igualdade), tradicionalmente em espanhol (na Espanha), a combinação de letras "ll" classifica entre "l" e "m", então "lz" <"ll" <"ma".


Você está falando sobre uma comparação insensível a um caso estúpido ou uma comparação Unicode normalizada completa?

Uma comparação burra não encontrará cadeias que podem ser as mesmas, mas não são binárias iguais.

Exemplo:

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

São todos equivalentes, mas também possuem diferentes representações binárias.

Dito isso, a unicode.org/reports/tr15 deve ser uma leitura obrigatória, especialmente se você planeja oferecer suporte a Hangul, Thaï e outros idiomas asiáticos.

Além disso, a IBM praticamente patenteou os algoritmos Unicode mais otimizados e os disponibilizou publicamente. Eles também mantêm uma implementação: IBM ICU


Apenas uma nota sobre qualquer método que você escolher, se esse método incluir o uso de strcmp sugerido por algumas respostas:

strcmp não funciona com dados Unicode em geral. Em geral, ele nem funciona com codificações Unicode baseadas em bytes, como utf-8, pois o strcmp só faz comparações de byte por byte e os pontos de código Unicode codificados em utf-8 podem levar mais de 1 byte. O único caso específico do Unicode que o strcmp manipula corretamente é quando uma string codificada com uma codificação baseada em byte contém apenas pontos de código abaixo de U + 00FF - então a comparação byte por byte é suficiente.


Se você tiver que comparar uma string de origem com mais frequência com outras strings, uma solução elegante é usar o regex.

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

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

Para minhas necessidades de comparação de cadeias insensíveis de maiúsculas e minúsculas, prefiro não ter que usar uma biblioteca externa, nem quero uma classe de cadeia separada com traços insensíveis a maiúsculas e minúsculas que seja incompatível com todas as minhas outras strings.

Então, o que eu tenho é o seguinte:

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

Uma função simples com uma sobrecarga para char e outra para whar_t. Não usa nada fora do padrão, então deve estar bem em qualquer plataforma.

A comparação de igualdade não considerará problemas como codificação de comprimento variável e normalização Unicode, mas basic_string não tem suporte para o que eu conheço de qualquer maneira e normalmente não é um problema.

Nos casos em que a manipulação lexicográfica mais sofisticada do texto é necessária, então você simplesmente tem que usar uma biblioteca de terceiros como o Boost, o que é esperado.


Aproveite as char_traits padrão. Lembre-se que um std::string é na verdade um typedef para std::basic_string<char> , ou mais explicitamente, std::basic_string<char, std::char_traits<char> > . O tipo char_traits descreve como os caracteres são comparados, como eles são copiados, como eles são convertidos, etc. Tudo o que você precisa fazer é digitar uma nova string sobre basic_string e fornecê-la com seus char_traits personalizados que a comparam insensivelmente.

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;

Os detalhes estão no Guru da Semana número 29 .


Se você não quiser usar a biblioteca Boost , aqui está a solução para ela usando apenas o cabeçalho io padrão 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;
}

No início de 2013, o projeto da UTI, mantido pela IBM, é uma ótima resposta para isso.

site.icu-project.org

O ICU é uma "biblioteca Unicode completa e portátil que acompanha de perto os padrões do setor". Para o problema específico da comparação de cadeias, o objeto Collation faz o que você deseja.

O Projeto Mozilla adotou a UTI para internacionalização no Firefox em meados de 2012; você pode acompanhar a discussão de engenharia, incluindo questões de sistemas de compilação e tamanho de arquivo de dados, aqui:


Se você estiver em um sistema POSIX, poderá usar o strcasecmp . Esta função não faz parte do padrão C, no entanto, nem está disponível no Windows. Isso executará uma comparação não diferencia maiúsculas de minúsculas em caracteres de 8 bits, desde que a localidade seja POSIX. Se a localidade não for POSIX, os resultados serão indefinidos (portanto, poderá fazer uma comparação localizada ou não). Um equivalente de caracteres largos não está disponível.

Caso contrário, um grande número de implementações históricas da biblioteca C possui as funções stricmp () e strnicmp (). O Visual C ++ no Windows renomeou todos eles prefixando-os com um sublinhado porque eles não fazem parte do padrão ANSI, portanto, nesse sistema eles são chamados de _stricmp ou _strnicmp . Algumas bibliotecas também podem ter funções equivalentes de caracteres largos ou multibyte (geralmente nomeadas, por exemplo, wcsicmp, mbcsicmp e assim por diante).

C e C ++ são amplamente ignorantes dos problemas de internacionalização, portanto, não há uma boa solução para esse problema, exceto para usar uma biblioteca de terceiros. Verifique o IBM ICU (Componentes Internacionais para Unicode) se você precisar de uma biblioteca robusta para C / C ++. O ICU é para sistemas Windows e Unix.


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

Você pode usar o código acima em C ++ 14 se não estiver em condições de usar o boost. Você tem que usar std::towlower para caracteres largos.


Tarde para a festa, mas aqui está uma variante que usa std::locale e, portanto, manipula corretamente o turco:

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

lhe dá um functor que usa a localidade ativa para converter caracteres em minúsculas, que você pode usar via std::transform para gerar strings em letras minúsculas:

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

Isso também funciona para strings baseadas em wchar_t .


Supondo que você esteja procurando um método e não uma função mágica que já exista, não há melhor maneira. Poderíamos todos escrever trechos de código com truques inteligentes para conjuntos de caracteres limitados, mas no final do dia em algum momento você tem que converter os caracteres.

A melhor abordagem para essa conversão é fazer isso antes da comparação. Isso permite uma boa dose de flexibilidade quando se trata de esquemas de codificação, que seu operador de comparação real deve ignorar.

É claro que você pode "ocultar" essa conversão por trás de sua função ou classe de string, mas ainda precisa converter as strings antes da comparação.


Eu escrevi uma versão de char_traits sem distinção entre maiúsculas e minúsculas para uso com std :: basic_string para gerar um std :: string que não diferencia maiúsculas de minúsculas ao fazer comparações, pesquisas, etc usando as funções de membro std :: basic_string internas.

Então, em outras palavras, eu queria fazer algo assim.

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

assert( a == b );

... qual std :: string não pode manipular. Aqui está o uso de meus novos char_traits:

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

assert( a == b );

... e aqui está a implementação:

/*  ---

        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;

O problema com o aumento é que você tem que ligar e depender de impulso. Não é fácil em alguns casos (por exemplo, android).

E usar char_traits significa que todas as suas comparações são insensíveis a maiúsculas e minúsculas, o que geralmente não é o que você deseja.

Isso deve ser suficiente. Deve ser razoavelmente eficiente. Não manipula unicode ou qualquer coisa embora.

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

Atualização: Bonus C ++ 14 version ( #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);
                      });
}

Você pode usar strcasecmp no Unix ou stricmp no Windows.

Uma coisa que não foi mencionada até agora é que, se você estiver usando strings stl com esses métodos, é útil comparar primeiro o comprimento das duas strings, já que essas informações já estão disponíveis para você na classe string. Isso pode impedir a comparação cara da string se as duas strings que você está comparando não tiverem o mesmo comprimento.


Estou tentando reunir uma boa resposta de todos os posts, então me ajude a editar isso:

Aqui está um método de fazer isso, embora ele não transforme as strings, e não seja amigável a Unicode, deve ser portátil, o que é um ponto positivo:

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

Pelo que li, isso é mais portável do que o stricmp () porque o stricmp () não faz parte da biblioteca std, mas é implementado apenas pela maioria dos fornecedores de compiladores.

Para obter uma implementação verdadeiramente amigável ao Unicode, você deve sair da biblioteca padrão. Uma boa biblioteca de terceiros é o IBM ICU (Componentes Internacionais para Unicode)

Também boost :: iequals fornece uma utilidade bastante boa para fazer este tipo de comparação.


Fazer isso sem usar o Boost pode ser feito obtendo o ponteiro de cadeia C com c_str() e usando strcasecmp :

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

O Boost inclui um algoritmo prático para isso:

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

Uma maneira simples de comparar duas cadeias em c ++ (testado para windows) é usar _stricmp

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

Se você estiver olhando para usar com std :: string, um exemplo:

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

Para mais informações, acesse: https://msdn.microsoft.com/it-it/library/e0z9k731.aspx


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

Demo



Apenas use strcmp() para case sensitive e strcmpi() ou stricmp() para comparação insensível a maiúsculas e minúsculas. Quais são ambos no arquivo de cabeçalho <string.h>

formato:

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

Uso:

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;

Saída

maçã e ApPlE são as mesmas

a vem antes de b, então a maçã vem antes da bola





string