std::range算法简介。
range算法被定义在头中,而range的基础结构和核心类型被定义在
下面,你可以找到显示标准算法和带有range的替代版本的例子。它们说明了一些基本概念,并尽量不使用高级的range组成或视图。我们将按照在cppreference/algorithms找到的顺序进行,在这一部分,我们将介绍 "非修改序列操作"。
1. all_of, any_of, none_of
标准算法
#include
#include
#include
#include
int main() {
const std::vector nums = {1, 2, 3, -4, 5, 6, 7, 8 };
auto is_positive = [](const auto& v) { return v > 0; };
// standard version:
auto res = std::all_of(begin(nums), end(nums), is_positive);
std::cout << "std::all_of: " << res << '\n';
res = std::any_of(begin(nums), end(nums), is_positive);
std::cout << "std::any_of: " << res << '\n';
}
range版本
// ranges version:
res = std::ranges::all_of(nums, is_positive);
std::cout << "std::ranges::all_of: " << res << '\n';
res = std::ranges::any_of(nums, is_positive);
std::cout << "std::ranges::any_of: " << res << '\n';
我们还可以写一个更复杂的例子,扫描一个自定义类型的容器。
#include
#include
#include
#include
struct Product {
std::string name_;
double value_ { 0.0 };
};
int main() {
const std::vector
{ "box", 10.0 }, {"tv", 100.0}, {"none", -1.0}
};
auto is_positive = [](const auto& v) { return v > 0; };
auto is_positive_val = [](const Product& p) {
return p.value_ > 0;
};
// standard version:
auto res = std::all_of(begin(prods), end(prods), is_positive_val);
std::cout << "std::all_of: " << res << '\n';
res = std::any_of(begin(prods), end(prods), is_positive_val);
std::cout << "std::any_of: " << res << '\n';
// ranges version:
res = std::ranges::all_of(prods, is_positive, &Product::value_);
std::cout << "std::ranges::all_of: " << res << '\n';
res = std::ranges::any_of(prods, is_positive, &Product::value_);
std::cout << "std::ranges::any_of: " << res << '\n';
}
在range版本中,我们仍然可以使用is_positive,一个通用的谓词,但我使用了一个投影,它只 "接受 "Product::value_并将其传入谓词。在标准情况下,我不得不写一个自定义的lambda来识别Product的类型。
2. for_each
一个基于范围的for循环的替代方案。
#include
#include
#include
#include
struct Product {
std::string name_;
double value_ { 0.0 };
};
int main() {
const std::vector
{ "box", 10.0 }, {"tv", 100.0}, {"none", -1.0}
};
auto out = [](const auto& v) { std::cout << v << ", "; };
// standard version:
std::cout << "std::for_each: \n";
std::for_each(begin(prods), end(prods), [](const Product& p){
std::cout << p.name_ << ", " << p.value_ << '\n';
});
std::cout << "std::for_each only names reverse: \n";
std::for_each(rbegin(prods), rend(prods), [](const Product& p){
std::cout << p.name_ << '\n';
});
// ranges version:
std::cout << "std::ranges::for_each: \n";
std::ranges::for_each(prods, [](const Product& p) {
std::cout << p.name_ << ", " << p.value_ << '\n';
});
std::cout << "std::ranges::for_each only names in reverse: \n";
std::ranges::for_each(prods | std::views::reverse,
out, &Product::name_);
}
令人振奋的是,在标准版本中以反向顺序打印需要使用rbegin/rend迭代器,然后使用一个自定义的单选函数来打印来自Product类的确切数据成员。而对于range,我们可以应用views::reverse,使用一个简单的输出函数,然后是一个投影。
缺少的是range算法的并行版本。
// standard:
std::for_each(std::execution::par, begin(prods), end(prods), /*...*/);
// no ranges version...
// std::ranges::for_each(std::execution::par, prods, /*... */); // doesn't compile...
所有的range算法都缺少并行版本,而不仅仅是for_each。
3. count_if
在下面的例子中,我们将计算名称以 "no "开头的Product。
#include
#include
#include
#include
struct Product {
std::string name_;
double value_ { 0.0 };
};
int main() {
const std::vector
{ "box", 10.0 }, {"tv", 100.0}, {"none", -1.0},
{ "car", 1000.0 }, {"toy", 40.0}, {"none", 0.0}
};
// standard version:
auto res = std::count_if(begin(prods), end(prods), [](const Product& p){
return p.name_.starts_with("no");
});
std::cout << "std::count_if: " << res << '\n';
// ranges version:
res = std::ranges::count_if(prods, [](const Product& p) {
return p.name_.starts_with("no");
});
std::cout << "std::ranges::count_if: " << res << '\n';
// alternative version for "none":
res = std::ranges::count(prods, std::string{"none"}, &Product::name_);
std::cout << "std::ranges::count: " << res << '\n';
}
这个例子显示了三种方法,最后一种使用了一个投影,只检查Product::name_数据成员。在这个方法中,我们精确地搜索 "none",所以它比starts_with更严格。
4. find_if
到目前为止,我们的文本算法都是返回布尔值或积分值,但有了find*函数,我们就有了显示更多内容的迭代器(或子range)。
请看这个例子。
#include
#include
#include
#include
struct Product {
std::string name_;
double value_ { 0.0 };
};
int main() {
const std::vector
{ "box", 10.0 }, {"tv", 100.0}, {"rocket", .0},
{ "car", 1000.0 }, {"toy", 40.0}, {"none", 0.0}
};
// standard version:
auto it = std::find_if(begin(prods), end(prods), [](const Product& p){
return p.name_.starts_with("ro");
});
if (it != end(prods))
std::cout << "std::find_if: " << it->name_ << '\n';
// ranges version:
auto res = std::ranges::find_if(prods, [](const Product& p) {
return p.name_.starts_with("ro");
});
if (res != end(prods))
std::cout << "std::ranges::find_if: " << res->name_ << '\n';
}
像其他算法一样,也有一个 "常规 "版本,你可以传递两个迭代器。
it = std::ranges::find_if(begin(prods), end(prods), [](const Product& p) {
return p.name_.starts_with("ro");
});
采取单一范围的版本是特殊的,因为它返回一个借用的迭代器。这种特殊类型允许检查临时/生命期对象的问题。当你传递两个迭代器时,这是不可能的(因为容器在某处存在),但用一个临时范围就可以。
struct Product {
std::string name_;
double value_ { 0.0 };
};
std::vector
return {
{ "box", 10.0 }, {"tv", 100.0}, {"rocket", .0},
{ "car", 1000.0 }, {"toy", 40.0}, {"none", 0.0}
};
}
int main() {
auto it = std::ranges::find_if(GetProds(), [](const Product& p) {
return p.name_.starts_with("ro");
});
std::cout << "std::ranges::find_if: " << it->name_ << '\n';
}
这不能编译,你会看到以下错误。
error: base operand of '->' has non-pointer type 'std::ranges::dangling'
22 | std::cout << "std::ranges::find_if: " << it->name_ << '\n';
| ^~
正如你所看到的,编译器检查了GetProds()返回一个临时的值,而我们会发现的迭代器是悬空的。
5. find_first_of
让我们来看看另一个find*函数的替代方案,它可以一次搜索多个元素。
#include
#include
#include
#include
struct Product {
std::string name_;
double value_ { 0.0 };
friend bool operator==(const Product& a, const Product& b) {
return a.name_ == b.name_ && abs(a.value_ - b.value_) < 0.0001;
}
};
int main() {
const std::vector
{ "box", 10.0 }, {"default", 0.0 }, {"tv", 100.0}, {"rocket", .0},
{ "car", 1000.0 }, {"toy", 40.0}, {"none", 0.0 }, { "ball", 40.0 }
};
const std::vector
{"default", 0.0 }, {"none", 0.0 }
};
// standard version:
auto it = std::find_first_of(begin(prods), end(prods), begin(invalids), end(invalids));
if (it != end(prods)) {
std::cout << "std::find_first_of: " << it->name_ << " at: "
<< std::distance(begin(prods), it) <<'\n';
auto it2 = std::find_first_of(std::next(it), end(prods), begin(invalids), end(invalids));
if (it2 != end(prods))
std::cout << "std::find_first_of: " << it2->name_ << " at: "
<< std::distance(begin(prods), it2) <<'\n';
}
// ranges version:
const std::array
auto res = std::ranges::find_first_of(prods, arrInvalids,
std::ranges::equal_to{}, &Product::name_);
if (res != end(prods)) {
const auto pos = std::distance(begin(prods), res);
std::cout << "std::ranges::find_first_of: " << res->name_
<< " at: " << pos <<'\n';
auto res2 = std::ranges::find_first_of(prods | std::views::drop(pos+1), arrInvalids,
std::ranges::equal_to{}, &Product::name_);
if (res2 != end(prods)) {
std::cout << "std::ranges::find_first_of: " << res2->name_
<< " at: " << std::distance(begin(prods), res2) <<'\n';
}
}
}
std::find_first_of需要两对迭代器。我想在例子中的prod序列中找到 "无效的 "product。因为我在比较product,所以我必须为我的结构定义operator==。另外,我可以提供一个二进制操作,然后只比较名称。
auto cmpNames = [](const Product& a, const Product& b) {
return a.name_ == b.name_;
};
auto it = std::find_first_of(begin(prods), end(prods),
begin(invalids), end(invalids), cmpNames);
if (it != end(prods)) {
// ...
}
在range版中,我可以使用预测和默认比较器来达到类似的效果。
const std::array
auto res = std::ranges::find_first_of(prods, arrInvalids,
std::ranges::equal_to{}, &Product::name_);
后来有趣的部分是,对于第二次搜索,我可以使用drop来跳过范围中的前N个元素。
auto res2 = std::ranges::find_first_of(prods | std::views::drop(pos+1),
arrInvalids, std::ranges::equal_to{}, &Product::name_);
另外,你也可以使用一个有两对迭代器的版本。
auto res2 = std::ranges::find_first_of(std::next(res), end(prods),
begin(arrInvalids), end(arrInvalids),
std::ranges::equal_to{}, &Product::name_);
6. mismatch
通过不匹配算法,我们可以找到两个范围不同的第一个地方。
#include
#include
#include
#include
#include
int main() {
const std::string firstStr = "Hello Super World";
const std::string secondStr = "Hello Amazing World";
std::cout << "mismatch for " << std::quoted(firstStr)
<< " and " << std::quoted(secondStr) << '\n';
// standard version:
auto [first, second] = std::mismatch(begin(firstStr), end(firstStr), begin(secondStr));
{
const auto pos = std::distance(begin(firstStr), first);
std::cout << "std::mismatch: at pos " << pos << '\n';
}
// ranges version:
auto res = std::ranges::mismatch(firstStr, secondStr);
{
const auto pos = std::distance(begin(firstStr), res.in1);
std::cout << "std::ranges::mismatch: at pos " << pos << '\n';
}
}
range版本返回:
template
using mismatch_result = ranges::in_in_result
这是一对两个迭代器,但我们可以通过.in1和.in2访问它们。
为什么不是一个简单的范围呢?在cpp参考中我们可以看到下面这句话。
Unlike std::pair and std::tuple, this class template has data members of meaningful names.
其结果在结构化绑定时表现不错,所以你可以写。
auto [n1, n2] = std::ranges::mismatch(firstStr, secondStr);
const auto pos = std::distance(begin(firstStr), n1);
std::cout << "std::ranges::mismatch: at pos " << pos << '\n';
代码几乎与标准版相同。
7. search
在另一个范围/容器中搜索模式。
#include
#include
#include
#include
#include
#include
int main() {
const std::string testString = "Hello Super World";
const std::string needle = "Super";
std::cout << "looking for " << std::quoted(needle)
<< " in " << std::quoted(testString) << '\n';
// standard version:
auto it = std::search(testString.begin(), testString.end(),
std::boyer_moore_searcher(needle.begin(), needle.end()));
if (it != testString.end()) {
const auto pos = std::distance(testString.begin(), it);
std::cout << "std::search: found at pos " << pos << '\n';
}
// ranges version:
auto res = std::ranges::search(testString, needle);
if (!res.empty()) {
const auto first = std::distance(testString.begin(), res.begin());
const auto last = std::distance(testString.begin(), res.end());
std::cout << "std::ranges::search: found between "
<< first << " and " << last << '\n';
}
}
标准版本返回第二个字符串开始的第一个字符串的迭代器(如果不在那里,则返回end())。而range版本则返回一个子range(或一个借用的子range)。
我们也可以使用投影以不区分大小写的方式进行检查。
// ranges version:
const std::string testString2 = "hello abc world";
const std::string needle2 = "ABC";
std::cout << "looking for " << std::quoted(needle2) << " in "
<< std::quoted(testString2) << '\n';
res = std::ranges::search(testString2, needle2,
std::ranges::equal_to{}, ::toupper, ::toupper);
if (!res.empty())
{
const auto first = std::distance(testString2.begin(), res.begin());
const auto last = std::distance(testString2.begin(), res.end());
std::cout << "std::ranges::search: found between "
<< first << " and " << last << '\n';
}
另一个函数 ranges::search_n可以方便地在输入范围内找到一个给定值的N个出现次数。
#include
#include
#include
#include
int main() {
const std::string sequence = "CTGCCCAGGGTTT";
const char letter = 'C';
const size_t count = 3;
std::cout << "looking for " << count << " "
<< letter << "'s in " << std::quoted(sequence) << '\n';
// standard version:
auto it = std::search_n(begin(sequence), end(sequence), count, letter);
if (it != end(sequence))
{
const auto pos = std::distance(begin(sequence), it);
std::cout << "std::search_n: found at pos " << pos << '\n';
}
// ranges version:
auto res = std::ranges::search_n(sequence, count, letter);
if (!res.empty())
{
const auto first = std::distance(begin(sequence), res.begin());
const auto last = std::distance(begin(sequence), res.end());
std::cout << "std::ranges::search_n: found between "
<< first << " and " << last << '\n';
}
}
在标准版本中,没有特殊的搜索器;你只能使用并行算法来调用它。
总结
在这篇文章中,我们涵盖了非修改性操作类别中的七种不同的算法 "类型":检查所有/无/部分元素的一些谓词,搜索,查找,一般迭代。总共有超过10个不同的例子。
ranges算法提供了一种更简单的方法来传递 "整个 "容器--只有一个参数,而不是传递给迭代器。它们也允许投影,并且有办法检测到临时范围的迭代器。它们也有局限性,比如缺乏高级搜索器或并行执行模式。
标签: 算法
②文章观点仅代表原作者本人不代表本站立场,并不完全代表本站赞同其观点和对其真实性负责。
③文章版权归原作者所有,部分转载文章仅为传播更多信息、受益服务用户之目的,如信息标记有误,请联系站长修正。
④本站一律禁止以任何方式发布或转载任何违法违规的相关信息,如发现本站上有涉嫌侵权/违规及任何不妥的内容,请第一时间反馈。发送邮件到 88667178@qq.com,经核实立即修正或删除。