如何在不使用循环的情况下简明地查找字符串中的所有数字?

时间:2014-12-17 23:42:03

标签: c++ c++11 c++14

我希望获得std::string中的所有数字,但没有使用循环(我自己;我正在调用的代码使用的是什么,我不介意)。请求的另一种观点是:从字符串中删除所有非数字,只留下数字。我知道我可以使用以下代码找到字符串中的所有数字:

std::string get_digits(std::string input) {
    std::string::size_type next_digit(0u);
    for (std::string::size_type pos(0u);
         input.npos != (pos = input.find_first_of("0123456789"));
         ++pos) {
        input[next_digit++] = input[pos];
    }
    input.resize(next_digit);
    return input;
}

但是,此函数使用循环。 std::string未提供函数find_all()或其他内容!理想情况下,字符串是就地移动的(上面的代码移动它,但很容易更改为参考)。

当有多种选择时,我会保证发布不同方法对某些冗长文本有多好处的分析结果。

9 个答案:

答案 0 :(得分:6)

一种方法是使用std::copy_if(或std::remove_if):

std::string get_digits(std::string input) {
    std::string result;
    std::copy_if(
        input.begin(), 
        input.end(), 
        std::back_inserter(result), 
        [](char c) { return '0' <= c && c <= '9'; });
    return result;
}

显然这在内部使用循环,但是你说你不关心那个......

修改:使用std::remove_if

std::string get_digits_remove(std::string input) {
    auto itErase = std::remove_if(
        input.begin(), 
        input.end(), 
        [](char c) { return !('0' <= c && c <= '9'); });
    input.erase(itErase, input.end());
    return input;
}

答案 1 :(得分:6)

虽然我主要希望得到5个快速答案(没有实现,但感叹),答案和评论导致了一些我没有想到过的有趣方法。我个人的期望是有效的答案会导致:

  • 如果您想要快速,请使用

    input.erase(std::remove_if(input.begin(), input.end(),
                               [](unsigned char c){ return !std::isdigit(c); }),
                input.end());
    
  • 如果您想简明扼要,请使用

    text = std::regex_replace(text, std::regex(R"(\D)"), "");
    

相反,有一些我甚至没有考虑过的方法:

  • 使用递归函数!

  • 使用似乎需要额外工作的std::partition()(保留将被抛出的字符)并更改顺序。

  • 使用std::stable_partition()似乎需要更多工作,但不会更改订单。

  • 使用std::sort()并使用相关字符提取子字符串,但我不知道如何保留原始字符序列。只使用稳定的版本并不完全。

将不同的方法放在一起,并使用了许多关于如何对字符进行分类的变体,导致总共17个版本的大致相同的操作(codegithub上)。大多数版本使用std::remove_if()std::string::erase(),但数字分类不同。

  1. remove_if()[](char c){ return d.find(c) == d.npos; })
  2. remove_if()[](char c){ return std::find(d.begin(), d.end(), c) == d.end(); }
  3. remove_if()[](char c){ return !std::binary_search(d.begin(), d.end()); }
  4. remove_if()[](char c){ return '0' <= c && c <= '9'; }
  5. remove_if()[](unsigned char c){ return !std::isdigit(c); } char作为unsigned char传递,以避免在cchar为负remove_if()时未定义的行为值)
  6. std::not1(std::ptr_fun(std::static_cast<int(*)(int)>(&std::isdigit))) std::isdigit()(必须使用强制转换来确定正确的重载:remove_if()恰好超载了。)
  7. [&](char c){ return !hash.count(c); }remove_if()
  8. [&](char c){ return filter[c]; }remove_if()(代码初始化实际上使用循环)
  9. [&](char c){ return std::isidigit(c, locale); }remove_if()
  10. [&](char c){ return ctype.is(std::ctype_base::digit, c); }str.erase(std::parition(str.begin(), str.end(), [](unsigned char c){ return !std::isdigit(c); }), str.end())
  11. str.erase(std::stable_parition(str.begin(), str.end(), [](unsigned char c){ return !std::isdigit(c); }), str.end())
  12. copy_if()
  13. 其中一个答案中描述的“排序方法”
  14. 其中一个答案中描述的text = std::regex_replace(text, std::regex(R"(\D)"), "");方法
  15. 递归方法在其中一个答案中描述
  16. test clang gcc icc 1 use_remove_if_str_find 22525 26846 24815 2 use_remove_if_find 31787 23498 25379 3 use_remove_if_binary_search 26709 27507 37016 4 use_remove_if_compare 2375 2263 1847 5 use_remove_if_ctype 1956 2209 2218 6 use_remove_if_ctype_ptr_fun 1895 2304 2236 7 use_remove_if_hash 79775 60554 81363 8 use_remove_if_table 1967 2319 2769 9 use_remove_if_locale_naive 17884 61096 21301 10 use_remove_if_locale 2801 5184 2776 11 use_partition 1987 2260 2183 12 use_stable_partition 7134 4085 13094 13 use_sort 59906 100581 67072 14 use_copy_if 3615 2845 3654 15 use_recursive 2524 2482 2560 16 regex_build 758951 531641 17 regex_prebuild 775450 519263 (我没有设法让它在icc上工作)
  17. 喜欢16但是已经建立了正则表达式
  18. 我在MacOS笔记本上运行了基准测试。由于使用Google Chars可以很容易地绘制这样的结果,here是结果的图形(尽管删除了使用regexp的版本,因为这些会导致图形缩放,使得有趣的位不可见)。表格形式的基准测试结果:

    {{1}}

答案 2 :(得分:2)

您可以使用std::partition

就地执行此操作
std::string get_digits(std::string& input)
{
    auto split =
        std::partition( std::begin(input), std::end(input), [](char c){return ::isdigit(c);} );

    size_t len = std::distance( std::begin(input), split );
    input.resize( len );
    return input;
}

std::partition不保证订单,因此如果订单很重要,请使用std::stable_partition

答案 3 :(得分:2)

我会从一个很好的原始函数开始,它组成你想要使用的std算法:

template<class Container, class Test>
void erase_remove_if( Container&& c, Test&& test ) {
  using std::begin; using std::end;
  auto it = std::remove_if( begin(c), end(c), std::forward<Test>(test) );
  c.erase( it, end(c) );
}

然后我们写保存数字:

std::string save_digits( std::string s ) {
  erase_remove_if( s,
    [](char c){
      if (c > '9') return true;
      return c < '0';
    }
  );
  return s;
}

答案 4 :(得分:1)

也许这个简单的答案就足够了?

std::string only_the_digits(std::string s)
{
    s.erase(std::remove_if(s.begin(), s.end(),
                           [](char c) { return !::isdigit(c); }), s.end());
    return s;
}

这种方法的缺点是无条件地创建输入数据的副本。如果有很多数字,那就没关系,因为我们正在重用那个对象。或者,您可以使此功能只是就地修改字符串(void strip_non_digits(std::string &)。)

但是如果只有几个数字而你想保持输入不变,那么你可能更喜欢创建一个新的(小)输出对象而不是复制输入。这可以通过输入字符串的参考视图来完成,例如,由基础TS提供,并使用copy_if

std::string only_the_digits(std::experimental::string_view sv)
{
    std::string result;
    std::copy_if(sv.begin(), sv.end(), std::back_inserter(::isdigit));
    return result;
}

答案 5 :(得分:1)

// terrible no-loop solution
void getDigs(const char* inp, char* dig)
{
    if (!*inp)
        return;
    if (*inp>='0' && *inp<='9')
    {
        *dig=*inp; 
        dig++;
        *dig=0;
    }
    getDigs(inp+1,dig);
}

答案 6 :(得分:1)

4个步骤中没有循环解决方案(但有错误检查,超过4个语句):

1)使用合适的排序(递增顺序)对字符串进行排序    ...现在所有数字都在一起,连接起来

2)使用std :: string.find_first_of()查找第一个数字的索引    (务必检查找到的数字)

3)使用std :: string.find_last_of()查找最后一位数的索引    (务必检查找到的数字)

4)使用std :: string :: substr()和前面的2个索引来提取数字

答案 7 :(得分:1)

我觉得这很简洁。

std::string get_digits(std::string input)
{
    input.erase(std::stable_partition(
                               std::begin(input),
                               std::end(input),
                               ::isdigit),
                std::end(input));

    return input;
}

特点:

  1. 按值传递sink参数以利用c ++ 11中的copy elision
  2. 保留数字顺序。
  3. 无用户代码 - 仅使用经过同行评审的stl函数。错误的机会 - 零。
  4. 这将是基于stl风格的迭代器方法:

    template<class InIter, class OutIter>
    OutIter collect_digits(InIter first, InIter last, OutIter first_out)
    {
        return std::copy_if(first, last, first_out, ::isdigit);
    }
    

    这有许多优点:

    1. 输入可以是任何可迭代的字符范围,而不仅仅是字符串
    2. 可以通过返回输出迭代器来链接
    3. 允许目标容器/迭代器(包括ostream_iterator)
    4. 带着一点爱,它可以用来处理unicode chars等
    5. 有趣的例子:

      #include <iostream>
      #include <vector>
      #include <string>
      #include <algorithm>
      #include <iterator>
      
      template<class InIter, class OutIter>
      OutIter collect_digits(InIter first, InIter last, OutIter first_out)
      {
          return std::copy_if(first, last, first_out, ::isdigit);
      }
      
      using namespace std;
      
      int main()
      {
          char chunk1[] = "abc123bca";
          string chunk2 { "def456fed" };
          vector<char> chunk3 = { 'g', 'h', 'i', '7', '8', '9', 'i', 'h', 'g' };
      
          string result;
          auto pos = collect_digits(begin(chunk1), end(chunk1), back_inserter(result));
          pos = collect_digits(begin(chunk2), end(chunk2), pos);
          collect_digits(begin(chunk3), end(chunk3), pos);
          cout << "first collect: " << result << endl;
      
          cout << "second collect: ";
          collect_digits(begin(chunk3),
                         end(chunk3),
                         collect_digits(begin(chunk2),
                                        end(chunk2),
                                        collect_digits(begin(chunk1),
                                                       end(chunk1),
                                                       ostream_iterator<char>(cout))));
          cout << endl;
      
          return 0;
      }
      

答案 8 :(得分:0)

只要#include <regex>出现在我之前,我就会使用这个单线宏,或者你包含它:

#define DIGITS_IN_STRING(a) std::regex_replace(a, std::regex(R"([\D])"), "")