Question

Quelle est la meilleure façon d'effectuer une comparaison de chaînes insensible à la casse en C++ sans transformer une chaîne en majuscules ou en minuscules ?

Veuillez indiquer si les méthodes sont compatibles Unicode et dans quelle mesure elles sont portables.

Était-ce utile?

La solution

Boost inclut un algorithme pratique pour cela :

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

Autres conseils

Bénéficiez de la norme char_traits.Rappelons qu'un std::string est en fait un typedef pour std::basic_string<char>, ou plus explicitement, std::basic_string<char, std::char_traits<char> >.Le char_traits type décrit comment les personnages se comparent, comment ils copient, comment ils diffusent, etc.Tout ce que vous avez à faire est de taper une nouvelle chaîne sur basic_string, et fournissez-lui votre propre personnalisation char_traits qui comparent les cas sans sensibilité.

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;

Les détails sont sur Gourou de la semaine numéro 29.

Le problème avec le boost est que vous devez vous connecter et dépendre de boost.Pas facile dans certains cas (par ex.Android).

Et utiliser char_traits signifie tous vos comparaisons ne sont pas sensibles à la casse, ce qui n'est généralement pas ce que vous souhaitez.

Cela devrait suffire.Cela devrait être raisonnablement efficace.Ne gère pas l'Unicode ou quoi que ce soit.

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

Mise à jour:Version bonus 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);
                      });
}

Si vous êtes sur un système POSIX, vous pouvez utiliser strcasecmp.Cependant, cette fonction ne fait pas partie du standard C et n'est pas disponible sous Windows.Cela effectuera une comparaison insensible à la casse sur les caractères 8 bits, tant que les paramètres régionaux sont POSIX.Si les paramètres régionaux ne sont pas POSIX, les résultats ne sont pas définis (il peut donc effectuer une comparaison localisée ou non).Un équivalent en caractères larges n’est pas disponible.

À défaut, un grand nombre d’implémentations historiques de bibliothèques C ont les fonctions stricmp() et strnicmp().Visual C++ sur Windows a renommé tous ces éléments en les préfixant d'un trait de soulignement car ils ne font pas partie de la norme ANSI, donc sur ce système, ils sont appelés _stricmp ou _strnicmp.Certaines bibliothèques peuvent également avoir des fonctions équivalentes à caractères larges ou multioctets (généralement nommées par ex.wcsicmp, mbcsicmp, etc.).

C et C++ ignorent tous deux les problèmes d'internationalisation. Il n'y a donc pas de bonne solution à ce problème, sauf à utiliser une bibliothèque tierce.Vérifier IBM ICU (Composants internationaux pour Unicode) si vous avez besoin d'une bibliothèque robuste pour C/C++.ICU est destiné aux systèmes Windows et Unix.

Parlez-vous d'une comparaison stupide insensible à la casse ou d'une comparaison Unicode entièrement normalisée ?

Une comparaison stupide ne trouvera pas de chaînes qui pourraient être identiques mais qui ne sont pas égales en binaire.

Exemple:

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

Sont tous équivalents mais ils ont également des représentations binaires différentes.

Cela dit, Normalisation Unicode devrait être une lecture obligatoire, surtout si vous prévoyez de prendre en charge le Hangul, le Thaï et d'autres langues asiatiques.

En outre, IBM a breveté les algorithmes Unicode les plus optimisés et les a rendus accessibles au public.Ils maintiennent également une implémentation : IBM ICU

boost::iequals n'est pas compatible utf-8 dans le cas d'une chaîne.Vous pouvez utiliser boost :: paramètres régionaux.

comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
  • Primaire : ignorez les accents et la casse des caractères, en comparant uniquement les lettres de base.Par exemple, « façade » et « Façade » sont identiques.
  • Secondaire : ignorez la casse des caractères mais tenez compte des accents."façade" et "façade" sont différents mais "Façade" et "façade" sont identiques.
  • Tertiaire – prenez en compte à la fois la casse et les accents :"Façade" et "façade" sont différents.Ignorez la ponctuation.
  • Quaternaire : tenez compte de la casse, des accents et de la ponctuation.Les mots doivent être identiques en termes de représentation Unicode.
  • Identique - comme quaternaire, mais comparez également les points de code.

Ma première idée pour une version non-Unicode a été de faire quelque chose comme ceci :


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

Vous pouvez utiliser strcasecmp sous Unix, ou stricmp sous Windows.

Une chose qui n'a pas été mentionnée jusqu'à présent est que si vous utilisez des chaînes stl avec ces méthodes, il est utile de comparer d'abord la longueur des deux chaînes, puisque cette information est déjà disponible dans la classe string.Cela pourrait éviter d'effectuer une comparaison de chaînes coûteuse si les deux chaînes que vous comparez n'ont même pas la même longueur en premier lieu.

Fonctions de chaîne Visual C++ prenant en charge Unicode : http://msdn.microsoft.com/en-us/library/cc194799.aspx

celui que vous recherchez probablement est _wcsnicmp

J'essaie de rassembler une bonne réponse à partir de tous les messages, alors aidez-moi à modifier ceci :

Voici une méthode pour y parvenir, même si elle transforme les chaînes et n'est pas compatible avec Unicode, elle devrait être portable, ce qui est 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 );
}

D'après ce que j'ai lu, c'est plus portable que stricmp() car stricmp() ne fait pas en fait partie de la bibliothèque std, mais uniquement implémenté par la plupart des fournisseurs de compilateurs.

Pour obtenir une implémentation véritablement conviviale pour Unicode, il semble que vous deviez sortir de la bibliothèque std.Une bonne bibliothèque tierce est la IBM ICU (Composants internationaux pour Unicode)

Aussi boost ::ieégal fournit un assez bon utilitaire pour effectuer ce type de comparaison.

Le Boost.String La bibliothèque dispose de nombreux algorithmes pour effectuer des comparaisons insensibles à la casse, etc.

Vous pourriez mettre en œuvre le vôtre, mais pourquoi s’embêter alors que cela a déjà été fait ?

POUR VOTRE INFORMATION, strcmp() et stricmp() sont vulnérables au débordement de tampon, car ils se contentent de traiter jusqu'à ce qu'ils atteignent un terminateur nul.C'est plus sûr à utiliser _strncmp() et _strnicmp().

Pour mes besoins de base en matière de comparaison de chaînes insensibles à la casse, je préfère ne pas avoir à utiliser une bibliothèque externe, et je ne souhaite pas non plus une classe de chaînes distincte avec des traits insensibles à la casse qui sont incompatibles avec toutes mes autres chaînes.

Donc ce que j'ai trouvé c'est ceci :

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

Une fonction simple avec une surcharge pour char et une autre pour whar_t.N'utilise rien de non standard et devrait donc convenir sur n'importe quelle plate-forme.

La comparaison d'égalité ne prendra pas en compte les problèmes tels que le codage de longueur variable et la normalisation Unicode, mais basic_string n'a aucun support pour cela à ma connaissance et ce n'est normalement pas un problème.

Dans les cas où une manipulation lexicographique plus sophistiquée du texte est requise, il vous suffit alors d'utiliser une bibliothèque tierce comme Boost, ce qui est normal.

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

Vous pouvez utiliser le code ci-dessus en C++14 si vous n'êtes pas en mesure d'utiliser boost.Vous devez utiliser std::towlower pour les caractères larges.

Court et sympa.Aucune autre dépendance que étendu std C lib.

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

Retour vrai si str1 et str2 sont égaux.strcasecmp n'existe peut-être pas, il pourrait y avoir des analogues stricmp, strcmpi, etc.

Exemple de code :

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

Sortir:

true
true
true
true
true

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

Démo

En supposant que vous recherchiez une méthode et non une fonction magique qui existe déjà, il n’y a franchement pas de meilleur moyen.Nous pourrions tous écrire des extraits de code avec des astuces astucieuses pour des jeux de caractères limités, mais en fin de compte, vous devez à un moment donné convertir les caractères.

La meilleure approche pour cette conversion est de le faire avant la comparaison.Cela vous offre une grande flexibilité en matière de schémas de codage, ce que votre opérateur de comparaison actuel devrait ignorer.

Vous pouvez bien sûr "masquer" cette conversion derrière votre propre fonction ou classe de chaîne, mais vous devez toujours convertir les chaînes avant la comparaison.

J'ai écrit une version insensible à la casse de char_traits à utiliser avec std::basic_string afin de générer une std::string qui n'est pas sensible à la casse lors des comparaisons, des recherches, etc. à l'aide des fonctions membres std::basic_string intégrées.

En d’autres termes, je voulais faire quelque chose comme ça.

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

assert( a == b );

... que std::string ne peut pas gérer.Voici l'utilisation de mes nouveaux char_traits :

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

assert( a == b );

...et voici la mise en œuvre :

/*  ---

        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;

Faire cela sans utiliser Boost peut être fait en obtenant le pointeur de chaîne C avec c_str() et en utilisant strcasecmp:

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

J'ai eu une bonne expérience en utilisant le Composants internationaux pour les bibliothèques Unicode - ils sont extrêmement puissants et fournissent des méthodes de conversion, de prise en charge des paramètres régionaux, de rendu de la date et de l'heure, de mappage de cas (ce que vous ne semblez pas vouloir), et collation, qui inclut une comparaison insensible à la casse et à l'accent (et plus encore).Je n'ai utilisé que la version C++ des bibliothèques, mais elles semblent également avoir une version Java.

Des méthodes existent pour effectuer des comparaisons normalisées comme mentionné par @Coincoin, et peuvent même prendre en compte les paramètres régionaux - par exemple (et ceci est un exemple de tri, pas strictement d'égalité), traditionnellement en espagnol (en Espagne), la combinaison de lettres "ll" trie entre "l" et "m", donc "lz" < "ll" < "ma".

Utilisez simplement strcmp() pour les cas sensibles à la casse et strcmpi() ou stricmp() pour une comparaison insensible à la casse.Les deux sont dans le fichier d'en-tête <string.h>

format:

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

Usage:

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;

Sortir

Apple et Apple sont identiques

a vient avant b, donc la pomme vient avant la balle

Juste une note sur la méthode que vous choisirez finalement, si cette méthode inclut l'utilisation de strcmp que certaines réponses suggèrent :

strcmp ne fonctionne pas avec les données Unicode en général.En général, cela ne fonctionne même pas avec les codages Unicode basés sur les octets, tels que utf-8, car strcmp n'effectue que des comparaisons octet par octet et les points de code Unicode codés en utf-8 peuvent prendre plus d'un octet.Le seul cas Unicode spécifique strcmp correctement géré, c'est lorsqu'une chaîne codée avec un codage basé sur les octets ne contient que des points de code inférieurs à U+00FF - alors la comparaison octet par octet est suffisante.

Début 2013, le projet ICU, maintenu par IBM, constitue une assez bonne réponse à cette problématique.

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

Les soins intensifs sont une "bibliothèque Unicode portable complète qui suit étroitement les normes de l'industrie". Pour le problème spécifique de la comparaison des chaînes, l'objet de collation fait ce que vous voulez.

Le projet Mozilla a adopté ICU pour l'internationalisation dans Firefox à la mi-2012 ;vous pouvez suivre la discussion technique, y compris les problèmes de systèmes de construction et de taille des fichiers de données, ici :

En retard à la fête, mais voici une variante qui utilise std::locale, et gère donc correctement le turc :

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

vous donne un foncteur qui utilise les paramètres régionaux actifs pour convertir les caractères en minuscules, que vous pouvez ensuite utiliser via std::transform pour générer des chaînes minuscules :

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

Cela fonctionne également pour wchar_t chaînes basées.

Il semble que les solutions ci-dessus n'utilisent pas la méthode de comparaison et n'implémentent pas à nouveau le total. Voici donc ma solution et j'espère qu'elle fonctionnera pour vous (elle fonctionne bien).

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

Si vous ne voulez pas utiliser Bibliothèque Boost alors voici une solution en utilisant uniquement l'en-tête io standard 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;
}

Si vous avez un vecteur de chaînes, par exemple :

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 vous devez comparer une chaîne source plus souvent avec d’autres chaînes, une solution élégante consiste à utiliser 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);

Un moyen simple de comparer deux chaînes en C++ (testé pour Windows) consiste à utiliser _stricmp

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

Si vous cherchez à utiliser avec std::string, un exemple :

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

Pour plus d’informations ici : 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;
        }
      }
}

cela pourrait probablement être rendu beaucoup plus efficace, mais voici une version volumineuse avec tous ses éléments nus.

pas si portable que ça, mais fonctionne bien avec tout ce qui se trouve sur mon ordinateur (aucune idée, je préfère les images et non les mots)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top