如何在不使用循环的情况下简洁地找到字符串中的所有数字
How can I concisely find all digits in a string without using a loop?
我想获取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()
功能或其他东西!理想情况下,字符串是就地操作的(上面的代码移动它,但它很容易更改为引用(。
当有多种选择时,我承诺会发布分析结果,说明不同方法在一些冗长的文本上的效果如何。
一种方法是使用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;
}
虽然我主要希望有 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 个版本大致相同的操作(代码在 github 上(。大多数版本使用std::remove_if()
和std::string::erase()
,但在数字分类上有所不同。
-
remove_if()
与[](char c){ return d.find(c) == d.npos; })
. -
remove_if()
[](char c){ return std::find(d.begin(), d.end(), c) == d.end(); }
-
remove_if()
与[](char c){ return !std::binary_search(d.begin(), d.end()); }
-
remove_if()
[](char c){ return '0' <= c && c <= '9'; }
-
remove_if()
[](unsigned char c){ return !std::isdigit(c); }
(char
作为unsigned char
传递,以避免在c
是负值char
时出现未定义的行为( -
remove_if()
std::not1(std::ptr_fun(std::static_cast<int(*)(int)>(&std::isdigit)))
(强制转换对于确定正确的过载是必要的:std::isdigit()
恰好过载(。 -
remove_if()
与[&](char c){ return !hash.count(c); }
-
remove_if()
[&](char c){ return filter[c]; }
(代码初始化实际上使用循环( -
remove_if()
[&](char c){ return std::isidigit(c, locale); }
-
remove_if()
[&](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())
-
str.erase(std::stable_parition(str.begin(), str.end(), [](unsigned char c){ return !std::isdigit(c); }), str.end())
- 其中一个答案中描述的"排序方法">
- 答案之一中描述的
copy_if()
方法 - 递归方法在其中一个答案中描述
-
text = std::regex_replace(text, std::regex(R"(D)"), "");
(我没有设法让它在 icc 上工作( - 像 16 但已经构建了正则表达式
我已经在MacOS笔记本电脑上运行了基准测试。由于这样的结果很容易用 Google Chars 绘制图表,因此这里是结果的图表(尽管删除了使用正则表达式的版本,因为这些版本会导致图表缩放,以至于有趣的位并不真正可见(。表格形式的基准测试结果:
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
我会从一个很好的原始函数开始,它组成了你要使用的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;
}
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
// 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);
}
也许简单的答案就足够了?
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;
}
4 个步骤没有循环解决方案(但有错误检查,超过 4 个语句(:
1( 对字符串进行排序,使用合适的排序(递增顺序( ...现在所有数字将在一起,
合并2( 使用 std::string.find_first_of(( 查找第一个数字的索引 (请务必检查找到的数字(
3( 使用 std::string.find_last_of(( 查找最后一个数字的索引 (请务必检查找到的数字(
4( 使用 std::string::substr(( 和前面的 2 个索引来提取数字
我认为这几乎是我所能得到的简洁。
std::string get_digits(std::string input)
{
input.erase(std::stable_partition(
std::begin(input),
std::end(input),
::isdigit),
std::end(input));
return input;
}
特征:
- 按值传递接收器参数以利用 C++11 中的复制 elision
- 保留数字顺序。
- 无用户代码 - 仅使用同行评审的 stl 函数。出现错误的几率 - 零。
这将是 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);
}
这有许多优点:
- 输入可以是任何可迭代的字符范围,而不仅仅是字符串
- 可以通过返回输出迭代器来链接
- 允许目标容器/迭代器(包括ostream_iterator(
- 只要有一点爱,它就可以处理 Unicode 字符等
有趣的例子:
#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;
}
只要#include <regex>
在它之前,或者你以其他方式包含它,我就会使用这个单行宏:
#define DIGITS_IN_STRING(a) std::regex_replace(a, std::regex(R"([D])"), "")
- 如何检查一个c++字符串中有多少相同的字符/数字
- 在 c++ 中查找字符串中没有循环的数字总和
- 将数字转换为填充字符串
- 递归形成字符串中所有数字字符的中间和?
- std::regex:匹配由数字和空格组成的字符串,并提取数字.如何?
- C++ python 中的函数返回数字而不是字符串
- C++:从文件中读取字符串和整数,并获得最大数字
- 以C++格式将字符串转换为数字
- C++关于数字的字符串数组
- 使用非托管导出将字符串从 C# 返回到C++将返回数字
- 我想使用 std::stoi 函数在 cpp 中将字符串转换为整数,因为我想在字符串中找到不同数字的总和(在下级酶中)
- 将大数字(10-12 位数字)存储在无序映射中<字符串,整数>
- 用C++将浮点数字转换为本地化字符串
- 如何在c++中将字符串中的所有数字保存到多维数组中
- 列出所有 N 位数字,并带有 [字符串] 数字
- 如何将字符串数字转换为各个数字的整数数组
- 如何将字符串/数字排序添加到 QSortFilterProxyModel 派生类
- 将std::字符串数字转换为十六进制数字
- 在c++ /Qt中拆分Lua函数调用的混合字符串/数字参数列表
- 获取字符串数字的精度