C++ 从字符串中间提取数字

C++ Extract number from the middle of a string

我有一个 vector 包含 strings 遵循 text_number-number

的格式

例如: Example_45-3

我只想要第一个数字(示例中的 45),我无法用我当前的代码做任何其他事情:

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

是否有更精简和更简单的方法来做到这一点?如果有,它们是什么?

我纯粹是出于想学习如何更好地编写代码的愿望,因为在一天结束时,上面的代码确实成功地提取了第一个数字,但它似乎冗长而迂回。

我可以想到两种方法:

  • 使用正则表达式
  • 使用迭代器遍历字符串,并将每个连续的数字复制到临时缓冲区。当它达到不合理的长度或在一串连续数字之后的第一个非数字时中断。然后你就有了一串可以轻松转换的数字。
std::string s = "Example_45-3";
int p1 = s.find("_");
int p2 = s.find("-");
std::string number = s.substr(p1 + 1, p2 - p1 - 1)

看看这个

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

这应该比 Ashot Khachatryan 的解决方案更有效。请注意使用 '_''-' 而不是 "_""-"。还有,搜索的起始位置 '-'.

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

如果您需要数字而不是字符串,就像 Alexandr Lapenkov 的解决方案所做的那样,您可能还想尝试以下方法:

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

您还可以使用内置的 find_first_offind_first_not_of 来查找任何字符串中的第一个 "numberstring"。

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

在 C++11 及更高版本中执行此操作的 'best' 方法可能是使用 regular expressions,当测试足够频繁地重复时,它结合了高表现力和高性能。

以下代码演示了基础知识。您应该 #include <regex> 才能正常工作。

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

更新为 C++11

(编译器正则表达式支持的重要说明:对于 gcc。您需要 4.9 或更高版本。我在 g++ 4.9[1] 和 9.2 版上测试了它。cppreference.com 在我使用的浏览器编译器中有。 )

感谢用户@2b-t 发现了 c++11 代码中的错误!

这是 C++11 代码:

#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("")
        );
    cout << input << endl;
    cout << output << endl;
}

只需要C++98的boost解决方案

适用于许多字符串的最小实现示例(不仅仅是 "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;
}

控制台输出:

Example_45-3
45

其他适用的示例字符串:

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

对于这个例子,我在 Linux 和 #include <boost/regex.hpp>-lboost_regex 上使用了 g++。您也可以使用 C++11x 正则表达式。

如果您有更好的正则表达式,请随时编辑我的解决方案。


评论:

如果没有性能限制,使用 Regex 是这类事情的理想选择,因为您不会重新发明轮子(通过编写一堆需要时间 write/test-fully 的字符串解析代码)。

此外 if/when 您的字符串变得更复杂或具有更多不同的模式正则表达式可以轻松适应复杂性。 (问题的示例模式很简单。但通常情况下,一个更复杂的模式需要 10-100 多行代码,而一行正则表达式会做同样的事情。)


[1]

[1] 显然,对 C++11 <regex> 的完全支持已为 g++ version 4.9.x and on Jun 26, 2015. Hat tip to SO questions #1 and #2 实现并发布,以确定编译器版本需要为 4.9.x.

使用@Pixelchemist 的回答,例如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;
}