【问题标题】:Parse comma-separated ints/int-ranges in C++在 C++ 中解析逗号分隔的整数/整数范围
【发布时间】:2020-12-06 07:41:52
【问题描述】:

给定一个包含范围和单个数字的 C++ 字符串:

"2,3,4,7-9"

我想把它解析成一个向量的形式:

2,3,4,7,8,9

如果数字由- 分隔,那么我想推送该范围内的所有数字。否则我想推送一个数字。

我尝试使用这段代码:

const char *NumX = "2,3,4-7";
std::vector<int> inputs;
std::istringstream in( NumX );
std::copy( std::istream_iterator<int>( in ), std::istream_iterator<int>(),
           std::back_inserter( inputs ) );

问题在于它不适用于范围。它只取字符串中的数字,而不是范围内的所有数字。

【问题讨论】:

  • 将字符串拆分为两个数字。然后从头到尾迭代添加数字。
  • 您可以使用find first of 查找范围并使用iota 填充它
  • 我建议 2 次通过。首先通过搜索逗号分隔成单独的块。然后为连字符解析每个块

标签: c++ string parsing split


【解决方案1】:

您的问题由两个独立的问题组成:

  1. ,处将字符串拆分为多个字符串
  2. 在解析每个字符串时将数字或数字范围添加到向量中

如果你先用逗号分割整个字符串,你就不必担心同时用连字符分割它。这就是您所说的分而治之方法。

, 拆分

This question 应该告诉你如何用逗号分割字符串。

解析并添加到std::vector&lt;int&gt;

用逗号分割字符串后,您只需为每个字符串调用此函数,将范围转换为单独的数字:

#include <vector>
#include <string>

void push_range_or_number(const std::string &str, std::vector<int> &out) {
    size_t hyphen_index;
    // stoi will store the index of the first non-digit in hyphen_index.
    int first = std::stoi(str, &hyphen_index);
    out.push_back(first);

    // If the hyphen_index is the equal to the length of the string,
    // there is no other number.
    // Otherwise, we parse the second number here:
    if (hyphen_index != str.size()) {
        int second = std::stoi(str.substr(hyphen_index + 1), &hyphen_index);
        for (int i = first + 1; i <= second; ++i) {
            out.push_back(i);
        }
    }
}

请注意,在连字符处拆分要简单得多,因为我们知道字符串中最多可以有一个连字符。在这种情况下,std::string::substr 是最简单的方法。请注意,如果整数太大而无法放入intstd::stoi 可能会引发异常。

【讨论】:

    【解决方案2】:

    到目前为止所有非常好的解决方案。使用现代 C++ 和正则表达式,您只需几行代码即可完成一体化解决方案。

    怎么样?首先,我们定义一个匹配整数或整数范围的正则表达式。它看起来像这样

    ((\d+)-(\d+))|(\d+)
    

    真的很简单。先说范围。所以,一些数字,后跟一个连字符和更多数字。然后是普通整数:一些数字。所有数字都分组。 (大括号)。连字符不在匹配组中。

    这一切都很简单,无需进一步解释。

    然后我们循环调用std::regex_search,直到找到所有匹配项。

    对于每个匹配,我们检查是否有子匹配,表示一个范围。如果我们有子匹配,一个范围,那么我们将子匹配之间的值(包括)添加到结果std::vector

    如果我们只有一个普通整数,那么我们只添加这个值。

    所有这些都给出了一个非常简单易懂的程序:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <regex>
    
    const std::string test{ "2,3,4,7-9" };
    
    const std::regex re{ R"(((\d+)-(\d+))|(\d+))" };
    std::smatch sm{};
    
    int main() {
        // Here we will store the resulting data
        std::vector<int> data{};
    
        // Search all occureences of integers OR ranges
        for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix()) {
    
            // We found something. Was it a range?
            if (sm[1].str().length())
    
                // Yes, range, add all values within to the vector  
                for (int i{ std::stoi(sm[2]) }; i <= std::stoi(sm[3]); ++i) data.push_back(i);
            else
                // No, no range, just a plain integer value. Add it to the vector
                data.push_back(std::stoi(sm[0]));
        }
        // Show result
        for (const int i : data) std::cout << i << '\n';
        return 0;
    }
    

    如果您还有其他问题,我很乐意回答。


    语言:C++ 17 使用 MS Visual Studio 19 社区版编译和测试

    【讨论】:

      【解决方案3】:

      考虑预处理您的数字字符串并将它们拆分。 在以下代码中,transform() 会将分隔符之一 , -+ 转换为空格,以便 std::istream_iterator 成功解析 int。

      #include <cstdlib>
      #include <algorithm>
      #include <string>
      #include <vector>
      #include <iostream>
      #include <sstream>
      
      int main(void)
      {
          std::string nums = "2,3,4-7,9+10";
          const std::string delim_to_convert = ",-+";  // , - and +
          std::transform(nums.cbegin(), nums.cend(), nums.begin(),
                  [&delim_to_convert](char ch) {return (delim_to_convert.find(ch) != string::npos) ? ' ' : ch; });
      
          std::istringstream ss(nums);
          auto inputs = std::vector<int>(std::istream_iterator<int>(ss), {});
      
          exit(EXIT_SUCCESS);
      }
      
      

      请注意,上面的代码只能拆分 1 字节长度的分隔符。如果您需要更复杂和更长的分隔符,您应该参考@d4rk4ng31 答案。

      【讨论】:

      • 你误解了这个问题。这不会解析范围,只会用2 3 4 7 9 10 填充向量。此外,您在string 之前忘记了std::,您需要将&lt;iterator&gt; 包含在std::istream_iterator 中。
      • @J.Schultke,您的评论指出了我的错误。没错,应该将 4-7 插入向量 4、5、6、7 中。缺少标题和省略 std:: 来自粘贴我的代码。
      【解决方案4】:

      除了@J。 Schultke 的优秀示例,我建议通过以下方式使用正则表达式:

      #include <algorithm>
      #include <iostream>
      #include <regex>
      #include <string>
      #include <vector>
      
      void process(std::string str, std::vector<int>& num_vec) {
          str.erase(--str.end());
          for (int i = str.front() - '0'; i <= str.back() - '0'; i++) {
              num_vec.push_back(i);                                                     
          }
      }
      
      int main() {
          std::string str("1,2,3,5-6,7,8");
          str += "#";
          std::regex vec_of_blocks(".*?\,|.*?\#");
          auto blocks_begin = std::sregex_iterator(str.begin(), str.end(), vec_of_blocks);
          auto blocks_end = std::sregex_iterator();
          std::vector<int> vec_of_numbers;
          for (std::sregex_iterator regex_it = blocks_begin; regex_it != blocks_end; regex_it++) {
              std::smatch match = *regex_it;
              std::string block = match.str();
              if (std::find(block.begin(), block.end(), '-') != block.end()) {
                  process(block, vec_of_numbers);
              }
              else {
                  vec_of_numbers.push_back(std::atoi(block.c_str()));
              }
          }
          return 0;
      }
      

      当然,您仍然需要一点点验证,不过,这会让您开始。

      【讨论】:

      • 嗯,总有许多不同的解决方案可能。使用正则表达式是一个绝妙的主意。但是,我会建议一个更简单的实现。也许在下面看到我的答案。但正如所说。有很多可能性。 . . .
      猜你喜欢
      • 1970-01-01
      • 2015-05-24
      • 1970-01-01
      • 2013-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多