[英]How to compose generators with STL algorithms
I have an algorithm which generates combinations from entries of a container and I want to find the combination which minimizes a cost function:我有一个从容器条目生成组合的算法,我想找到最小化成本的组合 function:
struct Vec { double x; double y; };
double cost( Vec a, Vec b ) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return dx*dx + dy*dy;
}
pair<Vec,Vec> get_pair_with_minimum_cost ( vector<Vec> inp, double (*cost_fun)(Vec,Vec) )
{
pair<Vec,Vec> result;
double min_cost = FLT_MAX;
size_t sz = inp.size();
for(size_t i=0; i<sz; i++) {
for (size_t j=i; j<sz; j++) {
double cost = cost_fun(inp[i], inp[j]);
if (cost < min_cost) {
min_cost = cost;
result = make_pair(inp[i], inp[j]);
}
}
}
return result;
}
vector <Vec> inp = {....};
auto best_pair = get_pair_with_minimum_cost ( inp, cost );
Unfortunately, get_pair_with_minimum_cost()
does 2 jobs:不幸的是,
get_pair_with_minimum_cost()
做了两个工作:
I could break them in two functions, like:我可以将它们分解为两个函数,例如:
template <class Func> void generate_all_combinations_of( vector<Vec> inp, Func fun ) { size_t sz = inp.size(); for(size_t i=0; i<sz; i++) { for (size_t j=i; j<sz; j++) { fun(make_pair(inp[i], inp[j])); } } }
std::min_element
on the output of the generator, iestd::min_element
,即vector<Vec> inp = {....}; vector<pair<Vec,Vec>> all_combinations; generate_all_combinations_of(inp, [&](vector<pair<Vec,Vec>> o){all_combinations.push_back(o); } ); auto best_pair = *min_element(all_combinations.begin(), all_combinations.end(), cost);
but I do not want the pay the cost of creating and extra container with temporary data ( all_combinations
).但我不想支付创建临时数据和额外容器的费用(
all_combinations
)。
Can I rewrite the generate_all_combinations_of()
such that it uses yield
or the new std::ranges
in such a way that I can combine it with STL algorithms such as find_if
, any_of
, min_element
or even adjacent_pair
?我可以重写
generate_all_combinations_of()
以便它使用yield
或新的std::ranges
以便我可以将它与 STL 算法(例如find_if
、 any_of
、 min_element
甚至adjacent_pair
)结合使用吗?
The great thing about this 'generator' function is that it is easy to read, so I would like to keep it as readable as possible.这个“生成器”function 的伟大之处在于它易于阅读,所以我想尽可能保持它的可读性。
NB: some of these algorithms need to break
the loop.注意:其中一些算法需要
break
循环。
What is the official name of combining entries this way?以这种方式组合条目的正式名称是什么? It this the combinations used in 'bubble-sort'.
这是“冒泡排序”中使用的组合。
Here's how I would write the function in c++20, using range views and algorithms so that there isn't a separate container that stores the intermediate results:以下是我如何使用范围视图和算法在 c++20 中编写 function,这样就没有单独的容器来存储中间结果:
double get_minimum_cost(auto const & inp)
{
namespace rs = std::ranges;
namespace rv = std::ranges::views;
// for each i compute the minimum cost for all j's
auto min_cost_from_i = [&](auto i)
{
auto costs_from_i = rv::iota(i + 1, inp.size())
| rv::transform([&](auto j)
{
return cost(inp[i], inp[j]);
});
return *rs::min_element(costs_from_i);
};
// compute min costs for all i's
auto all_costs = rv::iota(0u, inp.size())
| rv::transform(min_cost_from_i);
return *rs::min_element(all_costs);
}
Note that the solution doesn't compare the cost between same elements, since the cost
function example you showed would have a trivial result of 0. For a cost function that doesn't return 0, you can adapt the solution to generate a range from i
instead of i + 1
.请注意,该解决方案不会比较相同元素之间的成本,因为您展示的
cost
function 示例的结果微不足道,结果为 0。对于不返回 0 的成本 function,您可以调整解决方案以生成范围i
而不是i + 1
。 Also, if the cost
function is not symmetric, make that range start from 0 instead of i
.此外,如果
cost
function 不是对称的,则使该范围从 0 而不是i
开始。
Also, this function has UB if you call it with an empty range, so you should check for that as well.此外,如果您使用空范围调用此 function,则它具有 UB,因此您也应该检查它。
There is http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2168r0.pdf who's development I would follow有http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2168r0.pdf我会关注谁的发展
If you are using MSVC, and can use their experimental/generator (not sure if others support it yet), you can use如果你正在使用 MSVC,并且可以使用他们的实验/生成器(不确定其他人是否支持它),你可以使用
std::experimental::generator<std::size_t> Generate(std::size_t const end){
for(std::size_t i = 0; i < end; ++i)
co_yield i;
}
int main(){
auto vals = Generate(22);
auto const result = *std::min_element(std::begin(vals),std::end(vals));
std::cout <<'\n' << " " << result;
}
Here you would need to modify the Generate function to Yield a pair/or to yield cost在这里您需要修改 Generate function 以产生一对/或产生成本
(My recommendation would be to Keep things simple and yield the cost) (我的建议是保持简单并产生成本)
Then use vals to find min_cost然后使用 vals 找到 min_cost
Based on what I can find about the Ranges Proposal, it works on the basis of std::begin and std::end both of which experimental::generator
provides根据我能找到的有关 Ranges Proposal 的内容,它基于 std::begin 和 std::end 工作,两者都是
experimental::generator
提供的
So it should probably work所以它应该可以工作
Here's how I would write the function in c++17, using algorithms' min_element function, with no need for a separate container that stores the intermediate results.下面是我将如何在 c++17 中编写 function,使用算法的 min_element function,不需要单独的容器来存储中间结果。 I know you were looking for a c++20 solution, but this code does work fine under c++20, and perhaps it gives you some ideas about adapting functions to ranges when the range isn't just one of the ranges supplied by c++20's ranges library.
我知道您正在寻找一个 c++20 解决方案,但这段代码在 c++20 下工作正常,也许它给了您一些关于在范围不仅仅是由提供的范围之一时使函数适应范围的想法c++20 的范围库。
// TwoContainerRanger is an iterable container where the iterator consists
// of two indices that match the given filter, and whose iterators, when
// dereferenced, return the result of calling func with the
// elements of the two containers, at those two indices.
// filter can be nullptr.
template <typename Container1, typename Container2, typename Func>
struct TwoContainerRanger {
Container1 &c1;
Container2 &c2;
const Func &fun;
bool (*restriction)(size_t i1, size_t i2);
TwoContainerRanger(Container1 &container1, Container2 &container2,
bool (*filter)(size_t i1, size_t i2), const Func &func)
: c1(container1), c2(container2), fun(func), restriction(filter) {}
struct Iterator {
const TwoContainerRanger *gen;
size_t index1, index2;
auto &operator++() {
do {
if (++index1 == gen->c1.size()) {
if (++index2 == gen->c2.size()) {
// we leave both indices pointing to the end
// to indicate that we have reached the end.
return *this;
} else {
index1 = 0u;
}
}
} while (gen->restriction && gen->restriction(index1, index2) == false);
return *this;
}
bool operator==(const Iterator &other) const = default;
bool operator!=(const Iterator &other) const = default;
auto operator*() const {
return gen->fun(gen->c1[index1], gen->c2[index2]);
}
};
Iterator begin() {
Iterator b{this, size_t(0) - 1, 0u};
return ++b; // automatically applies the restriction
}
Iterator end() { return Iterator{this, c1.size(), c2.size()}; }
};
Calling it looks like this:调用它看起来像这样:
int main() {
std::array<Vec, 5> ar = {Vec{0, 0}, Vec{1, 1}, Vec{3, 3}, Vec{7, 7},
Vec{3.1, 3.1}};
TwoContainerRanger tcr{ar, ar, Triangle, cost};
auto result = std::min_element(tcr.begin(), tcr.end());
std::cout << "Min was at (" << result.index1 << "," << result.index2
<< "); cost was " << *result << '\n';
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.