简体   繁体   English

如何使用 STL 算法组合生成器

[英]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()做了两个工作:

  • generates the combinations生成组合
  • gets the minimum element获取最小元素

I could break them in two functions, like:我可以将它们分解为两个函数,例如:

  • the generator:发电机:
     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])); } } }
  • and then use std::min_element on the output of the generator, ie然后在生成器的 output 上使用std::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 )。

Questions:问题:

  1. 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_ifany_ofmin_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循环。

  2. 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);
}

Here's a demo .这是一个演示

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 followhttp://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

Ranges范围

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';
}

Here's a demo .这是一个演示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM