Confronto di stringhe senza distinzione tra maiuscole e minuscole in C++ [chiuso]

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

  •  08-06-2019
  •  | 
  •  

Domanda

Qual è il modo migliore per eseguire confronti di stringhe senza distinzione tra maiuscole e minuscole in C++ senza trasformare una stringa in tutte maiuscole o tutte minuscole?

Indica se i metodi sono compatibili con Unicode e quanto sono portabili.

È stato utile?

Soluzione

Boost include un pratico algoritmo per questo:

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

Altri suggerimenti

Approfitta della norma char_traits.Ricordiamo che a std::string è infatti una typedef per std::basic_string<char>, o più esplicitamente, std::basic_string<char, std::char_traits<char> >.IL char_traits type descrive come si confrontano i personaggi, come copiano, come lanciano, ecc.Tutto quello che devi fare è digitare una nuova stringa basic_string, e forniscilo con la tua personalizzazione char_traits che confrontano i casi in modo insensibile.

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;

I dettagli sono disponibili Guru della Settimana numero 29.

Il problema con Boost è che devi collegarti e dipendere da Boost.In alcuni casi non è facile (es.androide).

E usare char_traits significa Tutto i tuoi confronti non fanno distinzione tra maiuscole e minuscole, il che di solito non è quello che desideri.

Questo dovrebbe bastare.Dovrebbe essere ragionevolmente efficiente.Tuttavia non gestisce Unicode o altro.

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

Aggiornamento:Versione 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);
                      });
}

Se utilizzi un sistema POSIX, puoi utilizzare strcasecmp.Questa funzione però non fa parte del C standard, né è disponibile su Windows.Ciò eseguirà un confronto senza distinzione tra maiuscole e minuscole sui caratteri a 8 bit, purché la locale sia POSIX.Se la locale non è POSIX, i risultati non sono definiti (quindi potrebbe eseguire un confronto localizzato oppure no).Non è disponibile un equivalente a caratteri larghi.

In caso contrario, un gran numero di implementazioni storiche della libreria C hanno le funzioni stricmp() e strnicmp().Visual C++ su Windows li ha rinominati tutti prefissandoli con un carattere di sottolineatura perché non fanno parte dello standard ANSI, quindi su quel sistema vengono chiamati _stricmp o _strnicmp.Alcune librerie possono anche avere funzioni equivalenti a caratteri estesi o multibyte (tipicamente denominate ad es.wcsicmp, mbcsicmp e così via).

C e C++ sono entrambi in gran parte all'oscuro dei problemi di internazionalizzazione, quindi non esiste una buona soluzione a questo problema, se non quella di utilizzare una libreria di terze parti.Guardare IBM ICU (Componenti internazionali per Unicode) se hai bisogno di una libreria robusta per C/C++.ICU è sia per i sistemi Windows che per Unix.

Stai parlando di un confronto stupido senza distinzione tra maiuscole e minuscole o di un confronto Unicode completamente normalizzato?

Un confronto stupido non troverà stringhe che potrebbero essere uguali ma non sono uguali a livello binario.

Esempio:

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

Sono tutti equivalenti ma hanno anche rappresentazioni binarie diverse.

Detto ciò, Normalizzazione Unicode dovrebbe essere una lettura obbligatoria soprattutto se prevedi di supportare Hangul, Thaï e altre lingue asiatiche.

Inoltre, IBM ha praticamente brevettato gli algoritmi Unicode più ottimizzati e li ha resi disponibili al pubblico.Mantengono anche un'implementazione: terapia intensiva IBM

boost::iequals non è compatibile con utf-8 nel caso di string.Puoi usare boost::locale.

comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
  • Primario: ignora gli accenti e le maiuscole, confrontando solo le lettere base.Ad esempio "facciata" e "facciata" sono la stessa cosa.
  • Secondario: ignora i caratteri ma considera gli accenti."Facciata" e "Facciata" sono diversi ma "Facciata" e "Facciata" sono la stessa cosa.
  • Terziario: considera sia maiuscole che accenti:"Facciata" e "facciata" sono diversi.Ignora la punteggiatura.
  • Quaternario: considera tutte le maiuscole, gli accenti e la punteggiatura.Le parole devono essere identiche in termini di rappresentazione Unicode.
  • Identico: come quaternario, ma confronta anche i punti di codice.

Il mio primo pensiero per una versione non Unicode è stato quello di fare qualcosa del genere:


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

Puoi usare strcasecmp su Unix, o stricmp Su Windows.

Una cosa che non è stata menzionata finora è che se si utilizzano stringhe stl con questi metodi, è utile confrontare prima la lunghezza delle due stringhe, poiché questa informazione è già disponibile nella classe string.Ciò potrebbe impedire di eseguire il costoso confronto tra stringhe se le due stringhe che stai confrontando non hanno nemmeno la stessa lunghezza in primo luogo.

Funzioni stringa Visual C++ che supportano Unicode: http://msdn.microsoft.com/en-us/library/cc194799.aspx

quello che probabilmente stai cercando è _wcsnicmp

Sto cercando di mettere insieme una buona risposta da tutti i post, quindi aiutami a modificare questo:

Ecco un metodo per farlo, anche se trasforma le stringhe e non è compatibile con Unicode, dovrebbe essere portatile, il che è un vantaggio:

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

Da quello che ho letto questo è più portabile di stricmp() perché stricmp() in realtà non fa parte della libreria std, ma è implementato solo dalla maggior parte dei fornitori di compilatori.

Sembra che per ottenere un'implementazione veramente compatibile con Unicode sia necessario uscire dalla libreria std.Una buona libreria di terze parti è la IBM ICU (Componenti internazionali per Unicode)

Anche boost::iequals fornisce un'utilità abbastanza buona per eseguire questo tipo di confronto.

IL Boost.String La libreria ha molti algoritmi per eseguire confronti senza distinzione tra maiuscole e minuscole e così via.

Potresti implementarne uno tuo, ma perché preoccuparsi quando è già stato fatto?

PER TUA INFORMAZIONE, strcmp() E stricmp() sono vulnerabili all'overflow del buffer, poiché elaborano solo fino a quando non raggiungono un terminatore nullo.È più sicuro da usare _strncmp() E _strnicmp().

Per le mie esigenze di confronto tra stringhe senza distinzione tra maiuscole e minuscole, preferisco non dover utilizzare una libreria esterna, né voglio una classe di stringhe separata con tratti senza distinzione tra maiuscole e minuscole che sia incompatibile con tutte le mie altre stringhe.

Quindi quello che mi è venuto in mente è questo:

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 funzione semplice con un sovraccarico per char e un altro per whar_t.Non utilizza nulla di non standard, quindi dovrebbe andare bene su qualsiasi piattaforma.

Il confronto sull'uguaglianza non prenderà in considerazione problemi come la codifica a lunghezza variabile e la normalizzazione Unicode, ma basic_string non ha supporto per ciò di cui sono comunque a conoscenza e normalmente non è un problema.

Nei casi in cui è richiesta una manipolazione lessicografica del testo più sofisticata, è sufficiente utilizzare una libreria di terze parti come Boost, cosa prevedibile.

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

Puoi utilizzare il codice precedente in C++14 se non sei in grado di utilizzare boost.Devi usare std::towlower per caratteri larghi.

Breve e carino.Nessun'altra dipendenza, oltre a esteso std Clib.

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

ritorna VERO Se str1 E str2 sono uguali.strcasecmp potrebbe non esistere, potrebbero esserci analoghi stricmp, strcmpi, eccetera.

Codice di esempio:

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

Produzione:

true
true
true
true
true

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

Dimostrazione

Supponendo che tu stia cercando un metodo e non una funzione magica già esistente, francamente non esiste un modo migliore.Tutti potremmo scrivere frammenti di codice con trucchi intelligenti per set di caratteri limitati, ma alla fine ad un certo punto devi convertire i caratteri.

L'approccio migliore per questa conversione è farlo prima del confronto.Ciò ti consente una buona flessibilità quando si tratta di schemi di codifica, di cui il tuo attuale operatore di confronto dovrebbe essere all'oscuro.

Ovviamente puoi "nascondere" questa conversione dietro la tua funzione o classe di stringa, ma devi comunque convertire le stringhe prima del confronto.

Ho scritto una versione senza distinzione tra maiuscole e minuscole di char_traits da utilizzare con std::basic_string per generare una std::string che non fa distinzione tra maiuscole e minuscole quando si eseguono confronti, ricerche, ecc. utilizzando le funzioni membro std::basic_string integrate.

Quindi, in altre parole, volevo fare qualcosa del genere.

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

assert( a == b );

...che std::string non può gestire.Ecco l'utilizzo del mio nuovo char_traits:

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

assert( a == b );

...ed ecco l'implementazione:

/*  ---

        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;

Per fare ciò senza utilizzare Boost, è possibile ottenere il puntatore della stringa C con c_str() e utilizzando strcasecmp:

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

Ho avuto una buona esperienza nell'uso di Componenti internazionali per librerie Unicode - sono estremamente potenti e forniscono metodi per la conversione, supporto locale, rendering di data e ora, mappatura dei casi (che non sembra che tu voglia) e collazione, che include il confronto senza distinzione tra maiuscole e minuscole e accento (e altro ancora).Ho utilizzato solo la versione C++ delle librerie, ma sembra che abbiano anche una versione Java.

Esistono metodi per eseguire confronti normalizzati come indicato da @Coincoin e possono anche tenere conto della locale - ad esempio (e questo è un esempio di ordinamento, non strettamente di uguaglianza), tradizionalmente in spagnolo (in Spagna), la combinazione di lettere "ll" ordina tra "l" e "m", quindi "lz" < "ll" < "ma".

Basta usare strcmp() per la distinzione tra maiuscole e minuscole e strcmpi() O stricmp() per il confronto senza distinzione tra maiuscole e minuscole.Che sono entrambi nel file di intestazione <string.h>

formato:

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

Utilizzo:

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;

Produzione

mela e ApplE sono la stessa cosa

a viene prima di b, quindi la mela viene prima della palla

Solo una nota sul metodo che sceglierai alla fine, se tale metodo ne include l'uso strcmp che alcune risposte suggeriscono:

strcmp non funziona con i dati Unicode in generale.In generale, non funziona nemmeno con le codifiche Unicode basate su byte, come utf-8, poiché strcmp effettua solo confronti byte per byte e i punti di codice Unicode codificati in utf-8 possono richiedere più di 1 byte.L'unico caso Unicode specifico strcmp gestire correttamente è quando una stringa codificata con una codifica basata su byte contiene solo punti di codice inferiori a U+00FF, quindi il confronto byte per byte è sufficiente.

Dall’inizio del 2013, il progetto ICU, gestito da IBM, rappresenta un’ottima risposta a questo problema.

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

L'ICU è una "libreria Unicode portatile completa che tiene traccia di standard del settore". Per il problema specifico del confronto delle stringhe, l'oggetto di raccolta fa quello che desideri.

Il progetto Mozilla ha adottato ICU per l'internazionalizzazione in Firefox a metà del 2012;puoi tenere traccia della discussione tecnica, inclusi i problemi relativi ai sistemi di costruzione e alle dimensioni dei file di dati, qui:

Tardi alla festa, ma ecco una variante che utilizza std::locale, e quindi gestisce correttamente il turco:

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

ti dà un funtore che utilizza la locale attiva per convertire i caratteri in minuscolo, che puoi quindi utilizzare tramite std::transform per generare stringhe in minuscolo:

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

Questo funziona anche per wchar_t stringhe basate.

Sembra che le soluzioni di cui sopra non utilizzino il metodo di confronto e non implementino nuovamente il totale, quindi ecco la mia soluzione e spero che funzioni per te (funziona bene).

#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 non vuoi usare Potenzia la libreria quindi ecco la soluzione utilizzando solo l'intestazione 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;
}

Se hai un vettore di stringhe, ad esempio:

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 devi confrontare una stringa sorgente più spesso con altre stringhe, una soluzione elegante è usare 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 modo semplice per confrontare due stringhe in C++ (testato per Windows) è utilizzare _strimp

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

Se stai cercando di utilizzare con std::string, un esempio:

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

Per maggiori informazioni qui: 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;
        }
      }
}

probabilmente questo potrebbe essere reso molto più efficiente, ma ecco una versione ingombrante con tutti i suoi pezzi nudi.

non è poi così portatile, ma funziona bene con qualunque cosa sia sul mio computer (non ne ho idea, mi piacciono le immagini, non le parole)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top