Comparação de strings sem distinção entre maiúsculas e minúsculas em C++ [fechado]

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

  •  08-06-2019
  •  | 
  •  

Pergunta

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

Indique se os métodos são compatíveis com Unicode e quão portáveis ​​eles são.

Foi útil?

Solução

Boost inclui um algoritmo útil 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
}

Outras dicas

Aproveite o padrão char_traits.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 char_traits tipo descreve como os personagens se comparam, como eles copiam, como são lançados, etc.Tudo que você precisa fazer é digitar uma nova string basic_string, e forneça seu próprio costume char_traits que comparam maiúsculas de minúsculas sem distinção entre maiúsculas e minúsculas.

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 em Guru da Semana número 29.

O problema com o boost é que você precisa se vincular e depender dele.Não é fácil em alguns casos (por ex.andróide).

E usar char_traits significa todos suas comparações não diferenciam maiúsculas de minúsculas, o que geralmente não é o que você deseja.

Isto deveria ser suficiente.Deve ser razoavelmente eficiente.Não lida com unicode nem nada.

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

Atualizar:Versão bônus 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);
                      });
}

Se você estiver em um sistema POSIX, poderá usar strcasecmp.Esta função não faz parte do padrão C e nem está disponível no Windows.Isso executará uma comparação sem distinção entre maiúsculas e 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á ser feita 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 de bibliotecas C possuem 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, então nesse sistema eles são chamados _stricmp ou _strnicmp.Algumas bibliotecas também podem ter funções equivalentes de caracteres largos ou multibyte (normalmente denominadas, por exemplo,wcsicmp, mbcsicmp e assim por diante).

C e C++ ignoram amplamente questões de internacionalização, portanto não há uma boa solução para esse problema, exceto usar uma biblioteca de terceiros.Confira IBM ICU (Componentes Internacionais para Unicode) se você precisar de uma biblioteca robusta para C/C++.ICU é para sistemas Windows e Unix.

Você está falando de uma comparação idiota que não diferencia maiúsculas de minúsculas ou de uma comparação Unicode totalmente normalizada?

Uma comparação idiota não encontrará strings que possam ser iguais, mas não sejam binariamente 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 representações binárias diferentes.

Dito isto, Normalização Unicode deve ser uma leitura obrigatória, especialmente se você planeja apoiar Hangul, Thai e outras línguas asiáticas.

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

boost::iequals não é compatível com utf-8 no caso de string.Você pode usar impulsionar::localidade.

comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
  • Primário - ignora acentos e maiúsculas e minúsculas, comparando apenas letras básicas.Por exemplo, "fachada" e "Fachada" são iguais.
  • Secundário - ignore maiúsculas e minúsculas, mas considere acentos."fachada" e "fachada" são diferentes, mas "fachada" e "fachada" são iguais.
  • Terciário - considere maiúsculas e minúsculas e acentos:“Fachada” e “fachada” são diferentes.Ignore a pontuação.
  • Quaternário - considere todas as maiúsculas e minúsculas, acentos e pontuação.As palavras devem ser idênticas em termos de representação Unicode.
  • Idêntico - como quaternário, mas compare também os pontos de código.

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

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 primeiro comparar o comprimento das duas strings, uma vez que esta informação já está disponível para você na classe string.Isso pode evitar a dispendiosa comparação de strings se as duas strings que você está comparando não tiverem o mesmo comprimento.

Funções de string do Visual C++ com suporte a Unicode: http://msdn.microsoft.com/en-us/library/cc194799.aspx

o que você provavelmente está procurando é _wcsnicmp

Estou tentando reunir uma boa resposta de todas as postagens, então me ajude a editar isto:

Aqui está um método para fazer isso, embora transforme as strings e não seja compatível com Unicode, deve ser portátil, o que é uma vantagem:

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átil que stricmp() porque 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, parece que você deve sair da biblioteca std.Uma boa biblioteca de terceiros é a IBM ICU (Componentes Internacionais para Unicode)

Também boost::iequals fornece um utilitário bastante bom para fazer esse tipo de comparação.

O Impulsionar.String A biblioteca possui muitos algoritmos para fazer comparações que não diferenciam maiúsculas de minúsculas e assim por diante.

Você poderia implementar o seu próprio, mas por que se preocupar quando já foi feito?

PARA SUA INFORMAÇÃO, strcmp() e stricmp() são vulneráveis ​​ao buffer overflow, pois apenas processam até atingir um terminador nulo.É mais seguro usar _strncmp() e _strnicmp().

Para minhas necessidades básicas de comparação de strings sem distinção entre maiúsculas e minúsculas, prefiro não ter que usar uma biblioteca externa, nem quero uma classe de string separada com características que não diferenciam maiúsculas de minúsculas que sejam incompatíveis com todas as minhas outras strings.

Então o que eu descobri é 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, portanto deve funcionar em qualquer plataforma.

A comparação de igualdade não considerará questões como codificação de comprimento variável e normalização Unicode, mas basic_string não tem suporte para isso que eu saiba e normalmente não é um problema.

Nos casos em que é necessária uma manipulação lexicográfica de texto mais sofisticada, basta usar uma biblioteca de terceiros como o Boost, o que é de se esperar.

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.

curta e agradável. Há outras dependências, de estendido std C lib.

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

volta true se str1 e str2 são iguais. strcasecmp pode não existir, pode haver análogos stricmp, strcmpi, etc.

código Exemplo:

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

Output:

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

Demonstração

Assumindo que você está procurando um método e não uma função mágica que já existe, não é francamente há melhor maneira. Poderíamos todos os trechos de código de escrita com truques inteligentes para conjuntos de caracteres limitado, mas no final do dia no somepoint você tem que converter os caracteres.

A melhor abordagem para essa conversão é fazê-lo antes da comparação. Isto permite-lhe uma boa dose de flexibilidade quando se trata de esquemas de codificação, que o seu operador de comparação real deve ser ignorante.

Você pode, naturalmente, 'esconder' esta conversão por trás de sua própria função string ou classe, mas você ainda precisa converter as cordas antes de comparação.

Eu escrevi uma versão case-insensitive de char_traits para uso com std :: basic_string, a fim de gerar um std :: string que não é sensível a maiúsculas ao fazer comparações, pesquisas, etc usando o basic_string built-in std :: funções de membro.

Assim, em outras palavras, eu queria fazer algo como isto.

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

assert( a == b );

... que std :: string não pode manipular. Aqui está o uso dos 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;

Fazer isso sem usar Boost pode ser feito por obter o ponteiro de string 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 
}

Eu tive uma boa experiência usando o International Components for Unicode bibliotecas - eles são extremamente poderosos, e fornecer métodos para a conversão, suporte local, data e renderização tempo, mapeamento de caso (que você não parecem querer), e colação , que inclui caso- e acento-insensível comparação (e mais). Eu só usei a versão do C ++ das bibliotecas, mas eles parecem ter uma versão Java também.

Métodos existem para executar normalizada compara tal como referido por @Coincoin, e até mesmo pode explicar local - por exemplo (e este um exemplo de classificação, não estritamente a igualdade), tradicionalmente em espanhol (em Espanha), a combinação de letras "ll "sortes entre "l" e "m", por isso "lz"< "ll"< "ma".

Apenas uso strcmp() para maiúsculas e minúsculas e strcmpi() ou stricmp() para o caso comparação insensível. Que estão ambos no <string.h> arquivo de cabeçalho

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

Apple e Apple são as mesmas

a vem antes de b, então a Apple vem antes bola

Apenas uma nota sobre qualquer método que você escolhe finalmente, se esse método passa a incluir o uso de strcmp que algumas respostas sugerem:

strcmp não funciona com dados Unicode em geral. Em geral, ele não até mesmo trabalhar com codificações Unicode baseada em bytes, como utf-8, uma vez strcmp só faz comparações byte-por-byte e pontos de código Unicode codificados em UTF-8 pode levar mais de 1 byte. O caso Unicode strcmp apenas específicos adequadamente identificador é quando uma cadeia codificada com uma codificação baseada em bytes contém apenas código pontos abaixo L + 00FF - então a comparação byte-por-byte é o suficiente

.

A partir do início de 2013, o projeto UTI, mantidos pela IBM, é uma resposta muito boa para isto.

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

UTI é uma "completa biblioteca, portátil Unicode que acompanha de perto os padrões da indústria." Para o problema específico de comparação de string, o objeto Agrupamento faz o que quiser.

O Projeto Mozilla adotou UTI para a 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 do arquivo de dados, aqui:

atrasado para a festa, mas aqui é uma variante que usos std::locale, e, assim, maneja turco:

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

dá-lhe um functor que utiliza a região ativa para caracteres converter para minúsculas, que você pode usar via std::transform para gerar cadeias de minúsculas:

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

Isso também funciona para seqüências com base wchar_t.

Looks como soluções acima não estão usando o método de comparar e implementação total de novamente então aqui está a minha solução e espero que funcione para você (É bom trabalho).

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

Se você não quiser usar biblioteca de impulso , então aqui é solução para ele usando apenas C ++ padrão de 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;
}

Se você tem um vetor de strings, por exemplo:

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

Se você tem que comparar uma seqüência de origem com mais freqüência com outras cordas uma solução elegante é usar 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);

Uma maneira simples de comparar duas cordas em C ++ (testado para Windows) está usando _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 obter mais informações aqui: https://msdn.microsoft.com/ it-it / biblioteca / 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;
        }
      }
}

este provavelmente poderia ser feito muito mais eficiente, mas aqui é uma versão volumosos com todos os seus bits nu.

Não é tudo o que portátil, mas funciona bem com o que está no meu computador (nenhuma idéia, eu sou de fotos não palavras)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top