Comparación de cadenas insensibles a mayúsculas / minúsculas en C ++

¿Cuál es la mejor forma de hacer una comparación de cadenas insensible a mayúsculas y minúsculas en C ++ sin transformar una cadena en mayúsculas o minúsculas?

Indique si los métodos son amigables para Unicode y qué tan portátiles son.

Boost incluye un algoritmo útil para esto:

 #include  // Or, for fewer header dependencies: //#include  std::string str1 = "hello, world!"; std::string str2 = "HELLO, WORLD!"; if (boost::iequals(str1, str2)) { // Strings are identical } 

Aproveche los char_traits estándar. Recuerde que std::string es de hecho typedef para std::basic_string , o más explícitamente, std::basic_string > . El tipo char_traits describe cómo se comparan los personajes, cómo lo copian, cómo lo emiten, etc. Todo lo que necesita hacer es escribir una nueva cadena sobre basic_string y proporcionarle sus propios char_traits que comparen las mayúsculas y las minúsculas de forma insensible.

 struct ci_char_traits : public char_traits { 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 ci_string; 

Los detalles están en Guru of the Week número 29 .

¿Estás hablando de una comparación tonta insensible a mayúsculas o una comparación Unicode normalizada completa?

Una comparación tonta no encontrará cadenas que puedan ser iguales, pero que no sean binarias iguales.

Ejemplo:

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

Son todos equivalentes pero también tienen diferentes representaciones binarias.

Dicho esto, la Normalización Unicode debería ser una lectura obligatoria, especialmente si planeas apoyar Hangul, Thaï y otros idiomas asiáticos.

Además, IBM prácticamente patentó la mayoría de los algoritmos Unicode optimizados y los puso a disposición del público. También mantienen una implementación: IBM ICU

El problema con el impulso es que tienes que vincular y depender del impulso. No es fácil en algunos casos (por ejemplo, Android).

Y el uso de char_traits significa que todas sus comparaciones son insensibles a las mayúsculas y minúsculas, lo que generalmente no es lo que desea.

Esto debería ser suficiente. Debe ser razonablemente eficiente. No maneja unicode ni nada por el estilo.

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

Actualización: versión de Bonus C ++ 14 ( #include ):

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

Si está en un sistema POSIX, puede usar strcasecmp . Sin embargo, esta función no forma parte del estándar C ni está disponible en Windows. Esto realizará una comparación insensible a mayúsculas / minúsculas en caracteres de 8 bits, siempre que la configuración regional sea POSIX. Si la configuración regional no es POSIX, los resultados no están definidos (por lo que podría hacer una comparación localizada o no). Un equivalente de caracteres anchos no está disponible.

En su defecto, una gran cantidad de implementaciones históricas de bibliotecas C tienen las funciones stricmp () y strnicmp (). Visual C ++ en Windows renombró a todos estos prefijándolos con un guión bajo porque no son parte del estándar ANSI, por lo que en ese sistema se llaman _stricmp o _strnicmp . Algunas bibliotecas también pueden tener funciones equivalentes de caracteres anchos o multibyte (normalmente nombradas, por ejemplo, wcsicmp, mbcsicmp, etc.).

C y C ++ son en gran medida ignorantes de los problemas de internacionalización, por lo que no hay una buena solución a este problema, excepto para utilizar una biblioteca de terceros. Consulte IBM ICU (Componentes internacionales para Unicode) si necesita una biblioteca robusta para C / C ++. ICU es para sistemas Windows y Unix.

Mi primer pensamiento para una versión no unicode fue hacer algo como esto:

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

boost :: iequals no es compatible con utf-8 en el caso de la cadena. Puedes usar boost :: locale .

 comparator cmpr; cout < < (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") < < endl; 
  • Primario: ignore acentos y mayúsculas y minúsculas, comparando solo las letras de base. Por ejemplo, "fachada" y "Fachada" son lo mismo.
  • Secundario: ignora el caso del personaje pero considera los acentos. "fachada" y "fachada" son diferentes pero "Fachada" y "fachada" son las mismas.
  • Terciario: tenga en cuenta tanto el caso como los acentos: "Fachada" y "fachada" son diferentes. Ignora la puntuación.
  • Cuaternario: considere todos los casos, acentos y signos de puntuación. Las palabras deben ser idénticas en términos de representación Unicode.
  • Idéntico - como cuaternario, pero también compara puntos de código.

Funciones de cadena de Visual C ++ compatibles con Unicode: http://msdn.microsoft.com/en-us/library/cc194799.aspx

el que probablemente estés buscando es _wcsnicmp

Puede usar strcasecmp en Unix o stricmp en Windows.

Una cosa que no se ha mencionado hasta ahora es que si está utilizando stl strings con estos métodos, es útil comparar primero la longitud de las dos cadenas, ya que esta información ya está disponible para usted en la clase de cadena. Esto podría evitar hacer la costosa comparación de cadenas si las dos cadenas que está comparando no tienen ni siquiera la misma longitud en primer lugar.

Estoy tratando de improvisar una buena respuesta de todas las publicaciones, así que ayúdame a editar esto:

Este es un método para hacer esto, aunque transforma las cadenas, y no es amigable para Unicode, debería ser portátil, lo que es un plus:

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

Por lo que he leído, esto es más portable que stricmp () porque stricmp () no es de hecho parte de la biblioteca std, sino que solo es implementado por la mayoría de los proveedores de comstackdores.

Para obtener una implementación realmente amigable con Unicode, parece que debe salir de la biblioteca estándar. Una buena biblioteca de terceros es IBM ICU (Componentes Internacionales para Unicode)

También boost :: iequals proporciona una utilidad bastante buena para hacer este tipo de comparación.

La biblioteca Boost.String tiene muchos algoritmos para hacer comparaciones de casos insensibles, y así sucesivamente.

Podrías implementar el tuyo propio, pero ¿para qué molestarse cuando ya está hecho?

FYI, strcmp() y stricmp() son vulnerables al desbordamiento del búfer, ya que solo procesan hasta que tocan un terminador nulo. Es más seguro usar _strncmp() y _strnicmp() .

Para mis necesidades de comparación de cadenas insensibles a mayúsculas y minúsculas, prefiero no tener que usar una biblioteca externa, ni tampoco quiero una clase de cadena separada con rasgos que no distingan entre mayúsculas y minúsculas, que es incompatible con todas mis otras cadenas.

Entonces, lo que se me ocurrió es esto:

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

Una función simple con una sobrecarga para char y otra para whar_t. No utiliza nada no estándar, por lo que debería estar bien en cualquier plataforma.

La comparación de igualdad no tendrá en cuenta cuestiones como la encoding de longitud variable y la normalización de Unicode, pero basic_string no tiene soporte para eso de lo que estoy enterado de todos modos y normalmente no es un problema.

En los casos donde se requiere una manipulación lexicográfica más sofisticada de texto, entonces simplemente tiene que usar una biblioteca de terceros como Boost, que es de esperar.

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

Puede usar el código anterior en C ++ 14 si no está en posición de usar boost. Tienes que usar std::towlower para caracteres anchos.

Corto y agradable. No hay otras dependencias que la extendida std C lib.

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

devuelve verdadero si str1 y str2 son iguales. strcasecmp puede no existir, puede haber análogos stricmp , strcmpi , etc.

Código de ejemplo:

 #include  #include  #include  //For strcasecmp(). Also could be found in  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) < 

Salida:

 true true true true true 

Ver std::lexicographical_compare :

 // lexicographical_compare example #include  // std::cout, std::boolalpha #include  // std::lexicographical_compare #include  // std::tolower // a case-insensitive comparison function: bool mycomp (char c1, char c2) { return std::tolower(c1) 

Manifestación

Asumiendo que estás buscando un método y no una función mágica que ya existe, francamente, no hay una mejor manera. Todos podemos escribir fragmentos de código con trucos ingeniosos para conjuntos de caracteres limitados, pero al final del día en algún punto debe convertir los caracteres.

El mejor enfoque para esta conversión es hacerlo antes de la comparación. Esto le permite una gran flexibilidad cuando se trata de esquemas de encoding, que su operador de comparación real debería ignorar.

Por supuesto, puede “ocultar” esta conversión detrás de su propia función de cadena o clase, pero aún necesita convertir las cadenas antes de la comparación.

Escribí una versión de char_traits insensible a mayúsculas y minúsculas para su uso con std :: basic_string para generar una std :: string que no distingue entre mayúsculas y minúsculas al hacer comparaciones, búsquedas, etc. utilizando las funciones incorporadas de miembro std :: basic_string.

Entonces, en otras palabras, quería hacer algo como esto.

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

… which std :: string no puede manejar. Aquí está el uso de mis nuevos char_traits:

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

… y aquí está la implementación:

 /* --- 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 > noCaseString; A typedef is declared below which simplifies this use for chars: typedef std::basic_string > istring; --- */ template struct char_traits_nocase : public std::char_traits { 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 struct char_traits_nocase : public std::char_traits { 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 > istring; typedef std::basic_string > iwstring; 

Hacer esto sin usar Boost se puede hacer obteniendo el puntero de cadena C con c_str() y usando strcasecmp :

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

He tenido una buena experiencia en el uso de las bibliotecas de Componentes Internacionales para Unicode : son extremadamente potentes y proporcionan métodos de conversión, soporte de localización, representación de fecha y hora, mapeo de casos (que no parece querer) y recostackción . que incluye una comparación insensible a mayúsculas y minúsculas (y más). Solo he usado la versión C ++ de las bibliotecas, pero también parecen tener una versión Java.

Existen métodos para realizar comparaciones normalizadas según lo referido por @Coincoin, e incluso puede dar cuenta de la configuración regional, por ejemplo (y este es un ejemplo de clasificación, no estrictamente igualdad), tradicionalmente en español (en España), la combinación de letras “ll” ordena entre “l” y “m”, por lo que “lz” < "ll" <"ma".

Solo use strcmp() para mayúsculas y minúsculas y strcmpi() o stricmp() para comparar mayúsculas y minúsculas. Que están ambos en el archivo de encabezado

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<  

Salida

apple y ApPlE son lo mismo

a viene antes que b, entonces la manzana viene antes que la pelota

Solo una nota sobre el método que finalmente elija, si ese método incluye el uso de strcmp que algunas respuestas sugieren:

strcmp no funciona con datos Unicode en general. En general, ni siquiera funciona con codificaciones Unicode basadas en bytes, como utf-8, ya que strcmp solo hace comparaciones de byte por byte y los puntos de código Unicode codificados en utf-8 pueden tomar más de 1 byte. El único caso específico de strcmp Unicode que se strcmp correctamente es cuando una cadena codificada con una encoding basada en bytes contiene solo puntos de código por debajo de U + 00FF, entonces la comparación byte por byte es suficiente.

A principios de 2013, el proyecto ICU, mantenido por IBM, es una muy buena respuesta a esto.

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

ICU es una “biblioteca completa y portátil de Unicode que sigue de cerca los estándares de la industria”. Para el problema específico de la comparación de cadenas, el objeto Collation hace lo que desea.

El Proyecto Mozilla adoptó la ICU para la internacionalización en Firefox a mediados de 2012; Puede realizar un seguimiento de la discusión de ingeniería, incluidos los problemas de los sistemas de comstackción y el tamaño del archivo de datos, aquí:

Tarde a la fiesta, pero aquí hay una variante que usa std::locale , y por lo tanto maneja correctamente el turco:

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

le da un funtor que usa la configuración regional activa para convertir caracteres a minúsculas, que luego puede usar a través de std::transform para generar cadenas en minúsculas:

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

Esto también funciona para wchar_t basadas en wchar_t .

Parece que las soluciones anteriores no están usando el método de comparación y la implementación total otra vez, así que aquí está mi solución y espero que funcione para usted (está funcionando bien).

 #include #include #include using namespace std; string tolow(string a) { for(unsigned int i=0;i>str1>>str2; int temp=tolow(str1).compare(tolow(str2)); if(temp>0) cout< <1; else if(temp==0) cout<<0; else cout<<-1; } 

Si tiene un vector de cadenas, por ejemplo:

 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

Si tiene que comparar una cadena fuente más a menudo con otras cadenas, una solución elegante es usar expresiones regulares.

 std::wstring first = L"Test"; std::wstring second = L"TEST"; std::wregex pattern(first, std::wregex::icase); bool isEqual = std::regex_match(second, pattern); 

Si no desea utilizar la biblioteca de Boost , aquí tiene una solución con solo el encabezado estándar de C ++.

 #include  struct iequal { bool operator()(int c1, int c2) const { return std::toupper(c1) == std::toupper(c2); } }; bool iequals(const std::string&, const std::string&); int main(void) { std::string str_1 = "HELLO"; std::string str_2 = "hello"; if(iequals(str_1,str_2)) { std::cout< <"String are equal"< 

Una forma simple de comparar dos cadenas en c ++ (probado para Windows) es usando _stricmp

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

Si está buscando usar con std :: string, un ejemplo:

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

For more information here: 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; } } } 

this could probably be made much more efficient, but here is a bulky version with all its bits bare.

not all that portable, but works well with whatever is on my computer (no idea, I am of pictures not words)