【问题标题】:C++ Extract number from the middle of a stringC ++从字符串中间提取数字
【发布时间】:2015-05-06 10:16:10
【问题描述】:

我有一个 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); 
}

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

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

【问题讨论】:

  • 你考虑过正则表达式吗?
  • @senfen 我同意。如果没有性能限制(即每秒一千万次),只需使用one line regex。
  • @Trevor Boyd Smith,如果我们必须解析每秒百万次的流也是一个坏主意:)
  • 感谢您解决家庭作业问题...下次查找documentation。都在那里。
  • @rr- 我实际上是在自学,所以如果这个简单的问题惹恼了你,我很抱歉,但在这周之前我之前没有任何知识(由上面冗长的代码),我觉得我应该问。

标签: c++ string stringstream


【解决方案1】:

您还可以使用内置的find_first_of 和find_first_not_of 来查找任何字符串中的第一个“数字字符串”。

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

【讨论】:

  • 但是这样效率会降低。
  • @Lingxi:相比什么? find("_") + find("-")(带有" 或')暗示了关于字符串的几件事:它必须包括_ 和- && find("-") &gt; find("_")。我的代码根本不限制字符串的布局。
  • 当然这是一个更笼统的答案(你不能真的错了),但灵曦就手头的问题是正确的 - “我有一个包含 strings 的 vector格式为text_number-number"
  • @fakeaccount 我猜这两种方法的性能差异不容易衡量,除非设置具有大量连续执行的特定基准。在我看来,这个答案更适合 OP“希望学习如何更好地编码”,因为我说通用、清晰且仍然有效的代码“更好”或者更喜欢而不是编写适合特定极端情况的实现.
【解决方案2】:

这应该比 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);
}

【讨论】:

  • 此解决方案仅适用于非常特定的模式...即数字必须在其前面有一个“_”字符,在数字后面有一个“-”字符。像“asdfasdf 45 sdfsdf”或“X = 45, sdfsdf”这样的字符串不起作用。
  • @TrevorBoydSmith 是的,但根据 OP,数据将采用这种模式。
【解决方案3】:

为 C++11 更新

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

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

这里是 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("$1")
        );
    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 上使用了 g++ 和 #include &lt;boost/regex.hpp&gt; 和 -lboost_regex。您也可以使用 C++11x 正则表达式。

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


评论:

如果没有性能限制,则使用 Regex 非常适合此类事情,因为您无需重新发明轮子(通过编写一堆需要时间来编写/完全测试的字符串解析代码)。

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


[1]

[1] 显然,对 C++11 &lt;regex&gt; 的完全支持已为 g++ version 4.9.x and on Jun 26, 2015 实现并发布。提示 SO 问题 #1 和 #2 以确定编译器版本需要为 4.9.x。

【讨论】:

  • 除了指定 c++ 你只包括 &lt;regex&gt; 并将 boost:: 更改为 std:: (有些人不会知道 boost 是什么)非常可靠的答案。跨度>
  • 为什么,确切地说,你会考虑不同格式的字符串?如果它们应该采用这种格式,那么我认为允许使用不同格式的字符串将是一个错误。
  • 如果您想了解正则表达式语法的解释,我很乐意提供这样的解释。
  • @matthew 在我看来,原标题似乎是为了捕捉问题的意图......即对我来说,问题似乎更多是“通常如何从字符串中提取数字?”而不是“您将如何从仅使用此一种特定模式的字符串中提取数字?”。鉴于 OP 已经接受了这个解决方案,也许我的解释是正确的,也许这个问题可以澄清它是否需要像我这样的通用解决方案......或者像灵曦解决方案这样的特定解决方案。
【解决方案4】:

看看这个

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

【讨论】:

  • 当 OP 明确寻求 C++ 解决方案时的 C 解决方案。
  • @AlexandrLapenkov 如果你使用std::sscanf,没有人会反对它不是 C++。
  • @black 感谢您的建议 :) 我认为在这里大喊“这不是 C++!!!!”真的很愚蠢。
  • 具有 sscanf 已由您的编译器供应商调试的主要优势 - 获得正确的格式表达式,您的工作就完成了。其他“C++”解决方案(我同意这是一个红鲱鱼)涉及索引操作/减法/由 {+1/-1/+2} fencepost 错误关闭,并且在您依赖它之前需要进行很好的调试。请注意,@lingxi 的答案和这个答案都不能正确解释字符串中的格式错误。这个答案是通过检查 sscanf 的返回值来解决的,另一个答案需要在执行 string::substr 之前针对 std::string::npos 检查两个值。
【解决方案5】:

我可以想到两种方法:

  • 使用正则表达式
  • 使用迭代器遍历字符串,并将每个连续数字复制到临时缓冲区。当它达到不合理的长度或在一串连续数字之后的第一个非数字时中断。然后你就有了一串可以轻松转换的数字。

【讨论】:

【解决方案6】:
std::string s = "Example_45-3";
int p1 = s.find("_");
int p2 = s.find("-");
std::string number = s.substr(p1 + 1, p2 - p1 - 1)

【讨论】:

  • 如果存储 s.find("_") 的结果会更好
  • @Daniele 对,已修复。
【解决方案7】:

在 C++11 及更高版本中执行此操作的“最佳”方法可能是使用 regular expressions,当测试重复足够多时,它结合了高表达性和高性能。

以下代码演示了基础知识。你应该 #include &lt;regex&gt; 让它工作。

// 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】:

    使用@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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-17
      • 2016-06-24
      • 1970-01-01
      相关资源
      最近更新 更多