web-dev-qa-db-fra.com

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

J'utilise la méthode find() de std::string Pour tester si une chaîne 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 des chaînes, je peux toujours me tourner vers 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, je dois prendre en charge std::wstring/wchar_t. Des idées?

54
wpfwannabe

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

#include <locale>
#include <iostream>
#include <algorithm>
using namespace std;

// templated version of my_equal so it could work with both char and wchar_t
template<typename charT>
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_;
};

// find substring (case insensitive)
template<typename T>
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<typename T::value_type>(loc) );
    if ( it != str1.end() ) return it - str1.begin();
    else return -1; // not found
}

int main(int arc, char *argv[]) 
{
    // string test
    std::string str1 = "FIRST HELLO";
    std::string str2 = "hello";
    int f1 = ci_find_substr( str1, str2 );

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

    return 0;
}
69

Le nouveau style C++ 11:

#include <algorithm>
#include <string>
#include <cctype>

/// Try to find in the Haystack the Needle - ignore case
bool findStringIC(const std::string & strHaystack, const std::string & strNeedle)
{
  auto it = std::search(
    strHaystack.begin(), strHaystack.end(),
    strNeedle.begin(),   strNeedle.end(),
    [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
  );
  return (it != strHaystack.end() );
}

Une explication de la recherche std :: peut être trouvée sur cplusplus.com .

47
CC.

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

tolower

Remarquer:

12
DavidS

pourquoi ne pas utiliser Boost.StringAlgo:

#include <boost/algorithm/string/find.hpp>

bool Foo()
{
   //case insensitive find

   std::string str("Hello");

   boost::iterator_range<std::string::const_iterator> rng;

   rng = boost::ifind_first(str, std::string("EL"));

   return rng;
}
10
gast128

Puisque vous effectuez des recherches de sous-chaîne (std :: string) et non des recherches d'élément (caractère), il n'y a malheureusement aucune solution existante à ma connaissance qui soit immédiatement accessible dans la bibliothèque standard pour ce faire.

Néanmoins, c'est assez facile à faire: convertissez simplement les deux chaînes en majuscules (ou les deux en minuscules - j'ai choisi les 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 (à la limite du territoire de pessimisation) mais c'est la seule que je connaisse de façon autonome. Il n'est pas non plus difficile d'implémenter votre propre Finder de sous-chaîne insensible à la casse si vous êtes préoccupé par l'efficacité.

De plus, je dois 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 (changez simplement std :: string en std :: wstring).

[Modifier] Une alternative, comme indiqué, consiste à 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, comparaisons, etc. ne respectent pas la casse pour un type de chaîne donné.

8
stinky472

Il est également judicieux de fournir la version Boost: cela modifiera les chaînes d'origine.

#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 parfait booster la bibliothèque xpression

#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 ne contienne aucun caractère spécial regex.

3
Boris Ivanov

Si vous voulez une comparaison "réelle" selon Unicode et les règles locales, utilisez classe Collator IC .

2
Philipp

J'adore les réponses de Kiril V. Lyadvinsky et CC . mais mon problème était un peu plus spécifique que la simple insensibilité à la casse; J'avais besoin d'un analyseur d'arguments paresseux en ligne de commande pris en charge par Unicode qui pourrait éliminer les faux positifs/négatifs lors des recherches de chaînes alphanumériques qui pourraient avoir des caractères spéciaux dans la chaîne de base utilisée pour formater les mots clés alphanum que je recherchais, par exemple, Wolfjäger ne doit pas correspondre à jäger mais <jäger> devrait.

Il s'agit essentiellement de la réponse de Kiril/CC avec une gestion supplémentaire pour les correspondances alphanumériques de longueur exacte.

/* Undefined behavior when a non-alpha-num substring parameter is used. */
bool find_alphanum_string_CI(const std::wstring& baseString, const std::wstring& subString)
{
    /* Fail fast if the base string was smaller than what we're looking for */
    if (subString.length() > baseString.length()) 
        return false;

    auto it = std::search(
        baseString.begin(), baseString.end(), subString.begin(), subString.end(),
        [](char ch1, char ch2)
        {
            return std::toupper(ch1) == std::toupper(ch2);
        }
    );

    if(it == baseString.end())
        return false;

    size_t match_start_offset = it - baseString.begin();

    std::wstring match_start = baseString.substr(match_start_offset, std::wstring::npos);

    /* Typical special characters and whitespace to split the substring up. */
    size_t match_end_pos = match_start.find_first_of(L" ,<.>;:/?\'\"[{]}=+-_)(*&^%$#@!~`");

    /* Pass fast if the remainder of the base string where
       the match started is the same length as the substring. */
    if (match_end_pos == std::wstring::npos && match_start.length() == subString.length()) 
        return true;

    std::wstring extracted_match = match_start.substr(0, match_end_pos);

    return (extracted_match.length() == subString.length());
}
0
kayleeFrye_onDeck
#include <iostream>
using namespace std;

template <typename charT>
struct ichar {
    operator charT() const { return toupper(x); }
    charT x;
};
template <typename charT>
static basic_string<ichar<charT> > *istring(basic_string<charT> &s) { return (basic_string<ichar<charT> > *)&s; }
template <typename charT>
static ichar<charT> *istring(const charT *s) { return (ichar<charT> *)s; }

int main()
{
    string s = "The STRING";
    wstring ws = L"The WSTRING";
    cout << istring(s)->find(istring("str")) << " " << istring(ws)->find(istring(L"wstr"))  << endl;
}

Un peu sale, mais court et rapide.

0
ziomq1991