72 votes

Insensible à la casse std::string.find()

Je suis en train d'utiliser la méthode find() de std::string pour tester si une chaîne de caractères est une sous-chaîne d'une autre. Maintenant, j'ai besoin d'une version insensible à la casse de la même chose. Pour la comparaison de chaînes, je peux toujours utiliser stricmp() mais il ne semble pas y avoir de stristr().

J'ai trouvé diverses réponses et la plupart suggèrent d'utiliser Boost qui n'est pas une option dans mon cas. De plus, j'ai besoin de supporter std::wstring/wchar_t. Des idées ?

1 votes

Il y a un Gotw sur ce sujet très précis : gotw.ca/gotw/029.htm

1 votes

Stristr n'est pas là, mais "char *strcasestr(const char *haystack, const char *needle);" est là. N'est-ce pas correct?

0 votes

@Nasir, strcasestr n'est pas disponible sous Windows.

80voto

Kirill V. Lyadvinsky Points 47627

Vous pourriez utiliser std::search avec un prédicat personnalisé.

#include 
#include 
#include 
using namespace std;

// version modélisée de my_equal pour qu'elle puisse fonctionner avec char et wchar_t
template
struct my_equal {
    my_equal( const std::locale& loc ) : loc_(loc) {}
    bool operator()(charT ch1, charT ch2) {
        return std::toupper(ch1, loc_) == std::toupper(ch2, loc_);
    }
private:
    const std::locale& loc_;
};

// trouver sous-chaîne (insensible à la casse)
template
int ci_find_substr( const T& str1, const T& str2, const std::locale& loc = std::locale() )
{
    typename T::const_iterator it = std::search( str1.begin(), str1.end(), 
        str2.begin(), str2.end(), my_equal(loc) );
    if ( it != str1.end() ) return it - str1.begin();
    else return -1; // non trouvé
}

int main(int arc, char *argv[]) 
{
    // test de string
    std::string str1 = "PREMIER BONJOUR";
    std::string str2 = "bonjour";
    int f1 = ci_find_substr( str1, str2 );

    // test de wstring
    std::wstring wstr1 = L"ОПЯТЬ ПРИВЕТ";
    std::wstring wstr2 = L"привет";
    int f2 = ci_find_substr( wstr1, wstr2 );

    return 0;
}

0 votes

Pourquoi utilisez-vous des modèles ici?

0 votes

@rstackhouse, le modèle ici est pour un support de différents types de caractères (char et wchar_t).

1 votes

Merci, Kirill. Pour ceux aussi perdus que moi, insérez std::advance( it, offset ); après la déclaration de l'itérateur pour commencer la recherche à partir d'un décalage.

62voto

CC. Points 502

Le nouveau style C++11 :

#include 
#include 
#include 

/// Essayez de trouver dans la paille l'aiguille - en ignorant la casse
bool findStringIC(const std::string & strHaystack, const std::string & strNeedle)
{
  auto it = std::search(
    strHaystack.begin(), strHaystack.end(),
    strNeedle.begin(),   strNeedle.end(),
    [](unsigned char ch1, unsigned char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
  );
  return (it != strHaystack.end() );
}

L'explication de la fonction std::search peut être trouvée sur cplusplus.com.

0 votes

Que se passe-t-il si je veux trouver un caractère c dans une chaîne str en utilisant la même fonction. L'appeler en utilisant findStringIC(str, (string)c) ne fonctionne pas.

0 votes

Ce type de conversion de char en string ne fonctionne pas, vous devez réellement créer l'objet string comme std::string(1, 'x') Voir coliru.stacked-crooked.com/a/af4051dd1d15972e Si vous le faites souvent, il pourrait être utile de créer une fonction spécifique qui ne nécessite pas de créer un nouvel objet à chaque fois.

1 votes

Dans la plupart des cas, il est préférable d'utiliser tolower() lors d'une recherche insensible à la casse. Même Ada l'a changé en minuscules ! Il y a probablement des raisons que Unicode.org explique quelque part mais je ne sais pas exactement pourquoi.

17voto

Nimnio Points 184

Pourquoi ne pas simplement convertir les deux chaînes en minuscules avant d'appeler find()?

tolower

Remarque:

14 votes

Parce que c'est très inefficace pour les chaînes de caractères plus longues.

1 votes

Cela n'est également pas vraiment une bonne idée si votre logiciel doit un jour être localisé. Voir le test de la Turquie : haacked.com/archive/2012/07/05/…

0 votes

Les arguments que vous découvrirez pour effectuer des opérations de mise en majuscule et de mise en minuscule de base en C++ sur tout ce qui n'est pas encodé en ANSI vous submergeront xD En d'autres termes, ce n'est pas trivial pour la bibliothèque standard à partir de C++17.

8voto

stinky472 Points 4864

Étant donné que vous effectuez des recherches de sous-chaînes (std::string) et non des recherches d'éléments (caractères), il n'existe malheureusement aucune solution existante à ma connaissance immédiatement accessible dans la bibliothèque standard pour le faire.

Néanmoins, c'est assez facile à faire : il suffit de convertir les deux chaînes en majuscules (ou les deux en minuscules - j'ai choisi majuscules dans cet exemple).

std::string upper_string(const std::string& str)
{
    string upper;
    transform(str.begin(), str.end(), std::back_inserter(upper), toupper);
    return upper;
}

std::string::size_type find_str_ci(const std::string& str, const std::string& substr)
{
    return upper(str).find(upper(substr) );
}

Ce n'est pas une solution rapide (frôlant le territoire de la pessimization) mais c'est la seule que je connaisse de façon immédiate. Il n'est pas non plus très difficile d'implémenter votre propre recherche de sous-chaîne insensible à la casse si vous vous préoccupez de l'efficacité.

De plus, j'ai besoin de prendre en charge std::wstring/wchar_t. Des idées ?

tolower/toupper en locale fonctionnera également sur les chaînes larges, donc la solution ci-dessus devrait être tout aussi applicable (il suffit de changer std::string en std::wstring).

[Edit] Une alternative, comme indiqué, est d'adapter votre propre type de chaîne insensible à la casse à partir de basic_string en spécifiant vos propres traits de caractère. Cela fonctionne si vous pouvez accepter que toutes les recherches de chaînes, les comparaisons, etc. soient insensibles à la casse pour un certain type de chaîne donné.

2voto

Boris Ivanov Points 1383

Aussi il est logique de fournir la version Boost : cela modifiera les chaînes originales.

#include <boost/algorithm/string.hpp>

string str1 = "hello world!!!";
string str2 = "HELLO";
boost::algorithm::to_lower(str1)
boost::algorithm::to_lower(str2)

if (str1.find(str2) != std::string::npos)
{
    // str1 contains str2
}

ou en utilisant la parfaite bibliothèque d'expression boost

#include <boost/xpressive/xpressive.hpp>
using namespace boost::xpressive;
....
std::string long_string( "very LonG string" );
std::string word("long");
smatch what;
sregex re = sregex::compile(word, boost::xpressive::icase);
if( regex_match( long_string, what, re ) )
{
    cout << word << " found!" << endl;
}

Dans cet exemple, vous devez faire attention à ce que votre mot de recherche n'ait pas de caractères spéciaux regex.

1 votes

"... J'ai trouvé plusieurs réponses et la plupart suggèrent d'utiliser Boost, ce qui n'est pas une option dans mon cas".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X