web-dev-qa-db-fra.com

C++ Extrait le numéro du milieu d'une chaîne

J'ai une vector contenant strings qui suit le format de text_number-number 

Exemple: Example_45-3

Je veux seulement le premier numéro (45 dans l'exemple) et rien d'autre que je puisse faire avec mon code actuel:

std::vector<std::string> imgNumStrVec;
for(size_t i = 0; i < StrVec.size(); i++){
    std::vector<std::string> seglist;
    std::stringstream ss(StrVec[i]);
    std::string seg, seg2;
    while(std::getline(ss, seg, '_')) seglist.Push_back(seg);
    std::stringstream ss2(seglist[1]);
    std::getline(ss2, seg2, '-');
    imgNumStrVec.Push_back(seg2); 
}

Existe-t-il des moyens plus simples et plus simples de procéder? et si oui quels sont-ils?

Je demande purement par désir d’apprendre à mieux coder car au bout du compte, le code ci-dessus n’extrait avec succès que le premier chiffre, mais il semble long et arrondi.

23
fakeaccount

mis à jour pour C++ 11 2018-12-04

J'ai essayé de mettre à jour cette réponse pour utiliser C++ 11 sur ma machine, mais cela a échoué car mon compilateur g ++ ne dispose pas du support complet de <regex> ... donc je continuais à obtenir des exceptions non capturées std::regex_errorcode=4 (c'est-à-dire "crochet manquant") pour toutes les expressions rationnelles avec crochet classes de caractères std::regex("[0-9]").

Apparemment, le support complet pour C++ 11 <regex> a été implémenté et publié pour g ++ version 4.9.x et le 26 juin 2015 . La pointe du chapeau à SO questions # 1 et # 2 pour déterminer la version du compilateur devant être 4.9.x.

Voici le code C++ 11 qui devrait fonctionner mais je n'ai pas pu le tester:

#include <iostream>
#include <string>
#include <regex>

using std::cout;
using std::endl;

int main() {
    std::string input = "Example_45-3";
    std::string output = std::regex_replace(
        input,
        std::regex("[^0-9]*([0-9]+).*"),
        std::string("\\1")
        );
    cout << input << endl;
    cout << output << endl;
}

solution boost qui nécessite uniquement C++ 98

Exemple d'implémentation minimale fonctionnant sur de nombreuses chaînes (pas uniquement les chaînes de la forme "text_45-text":

#include <iostream>
#include <string>
using namespace std;
#include <boost/regex.hpp>

int main() {
    string input = "Example_45-3";
    string output = boost::regex_replace(
        input,
        boost::regex("[^0-9]*([0-9]+).*"),
        string("\\1")
        );
    cout << input << endl;
    cout << output << endl;
}

sortie de la console:

Example_45-3
45

Autres exemples de chaînes sur lesquelles cela fonctionnerait:

  • "asdfasdf 45 sdfsdf" 
  • "X = 45, sdfsdf"

Pour cet exemple, j'ai utilisé g ++ sous Linux avec #include <boost/regex.hpp> et -lboost_regex. Vous pouvez également utiliser regex C++ 11x.

N'hésitez pas à modifier ma solution si vous avez une meilleure expression rationnelle.


Commentaire:

S'il n'y a pas de contraintes de performances, utiliser Regex est idéal pour ce genre de choses, car vous ne réinventez pas la roue (en écrivant un tas de code d'analyse de chaînes qui prend du temps à écrire/à tester de manière exhaustive).

De plus, si/quand vos chaînes deviennent plus complexes ou ont des modèles plus variés, regex s'adapte facilement à la complexité. (L'exemple de motif de la question est assez simple. Mais souvent, un motif plus complexe prend entre 10 et plus de 100 lignes de code lorsqu'une expression rationnelle à une ligne fait de même.)

10

Vous pouvez également utiliser les find_first_of et find_first_not_of intégrés pour trouver le premier "numberstring" dans une chaîne.

std::string first_numberstring(std::string const & str)
{
  std::size_t const n = str.find_first_of("0123456789");
  if (n != std::string::npos)
  {
    std::size_t const m = str.find_first_not_of("0123456789", n);
    return str.substr(n, m != std::string::npos ? m-n : m);
  }
  return std::string();
}
19
Pixelchemist

Cela devrait être plus efficace que la solution d'Ashot Khachatryan. Notez l'utilisation de '_' et '-' au lieu de "_" et "-". Et aussi, la position de départ de la recherche pour '-'.

inline std::string mid_num_str(const std::string& s) {
    std::string::size_type p  = s.find('_');
    std::string::size_type pp = s.find('-', p + 2); 
    return s.substr(p + 1, pp - p - 1);
}

Si vous avez besoin d'un nombre au lieu d'une chaîne, comme ce que la solution d'Alexandr Lapenkov a fait, essayez également les solutions suivantes:

inline long mid_num(const std::string& s) {
    return std::strtol(&s[s.find('_') + 1], nullptr, 10);
}
18
Lingxi

Regarde ça

std::string ex = "Example_45-3";
int num;
sscanf( ex.c_str(), "%*[^_]_%d", &num );
13

Je peux penser à deux façons de le faire:

  • Utilisez des expressions régulières
  • Utilisez un itérateur pour parcourir la chaîne et copiez chaque chiffre consécutif dans un tampon temporaire. Pause quand elle atteint une longueur déraisonnable ou le premier non-chiffre après une chaîne de chiffres consécutifs. Ensuite, vous avez une chaîne de chiffres que vous pouvez facilement convertir.
12
Diogo Cunha
std::string s = "Example_45-3";
int p1 = s.find("_");
int p2 = s.find("-");
std::string number = s.substr(p1 + 1, p2 - p1 - 1)
9

La meilleure façon de le faire dans C++ 11 et versions ultérieures est probablement d'utiliser expressions régulières , qui combinent une expressivité élevée et des performances élevées lorsque le test est répété suffisamment souvent.

Le code suivant illustre les bases. Vous devriez #include <regex> pour que cela fonctionne.

// The example inputs
std::vector<std::string> inputs {
    "Example_0-0", "Example_0-1", "Example_0-2", "Example_0-3", "Example_0-4",
    "Example_1-0", "Example_1-1", "Example_1-2", "Example_1-3", "Example_1-4"
};

// The regular expression. A lot of the cost is incurred when building the
// std::regex object, but when it's reused a lot that cost is amortised.
std::regex imgNumRegex { "^[^_]+_([[:digit:]]+)-([[:digit:]]+)$" };

for (const auto &input: inputs){
    // This wil contain the match results. Parts of the regular expression
    // enclosed in parentheses will be stored here, so in this case: both numbers
    std::smatch matchResults;

    if (!std::regex_match(input, matchResults, imgNumRegex)) {
        // Handle failure to match
        abort();
    }

    // Note that the first match is in str(1). str(0) contains the whole string
    std::string theFirstNumber = matchResults.str(1);
    std::string theSecondNumber = matchResults.str(2);

    std::cout << "The input had numbers " << theFirstNumber;
    std::cout << " and " << theSecondNumber << std::endl;
}
8
Thierry

En utilisant la réponse de @ Pixelchemist et par exemple std::stoul:

bool getFirstNumber(std::string const & a_str, unsigned long & a_outVal)
{
    auto pos = a_str.find_first_of("0123456789");

    try
    {
        if (std::string::npos != pos)
        {
            a_outVal = std::stoul(a_str.substr(pos));

            return true;
        }
    }
    catch (...)
    {
        // handle conversion failure
        // ...
    }

    return false;
}
0
darkbit