【问题标题】:How to iterate over two STL-like containers (Cartesian product)如何迭代两个类似 STL 的容器(笛卡尔积)
【发布时间】:2013-05-22 08:26:26
【问题描述】:

我想通过 BOOST 减少以下内容

typedef std::vector<int>::const_iterator Iterator;

for(Iterator i = v1.begin(), ie = v1.end(); i != ie; ++i) {
  for(Iterator j = v2.begin(), je = v2.end(); j != je; ++j) {
    doSomething( *i, *j );
  }
}

我的意思是将 2 个循环封装在一个构造中(使用 Boost.Foreach、Boost.Range、Boost.Iterator 等)。以下是我想看的点赞(只是想法,不完全是我想看的)

BOOST_FOREACH(boost::tuple<int,int> &p, ..._range(v1, v2)) {
  doSomething(p.get<0>(), p.get<1>());
}

怎么做?

编辑:顺便说一句,在python中你可以只写

for (x1,x2,x3) in (l1,l2,l3):
print "do something with", x1, x2, x3

【问题讨论】:

  • 有什么理由不能将上述内容放在可重用函数中?
  • @KerrekSB 这样的转发迭代器并不像应有的那么简单,因为在内部递增时,您必须知道何时到达终点。您无法从单个迭代器中知道这一事实是 STL 中的一个主要设计缺陷,并且会导致无穷无尽的问题。
  • @KerrekSB 我明白这一点。您的派生迭代器变得更加复杂。当我需要一个过滤迭代器时,我遇到了同样的问题,并且 boost::iterator 经常因为它而更重且更难使用:当调用构造函数时,你必须向它传递四个迭代器,而不仅仅是两个,当然,您需要为迭代构造两个迭代器。总之,由于 STL 的糟糕设计,您总共传递了 8 个参数,而不仅仅是 2 个。
  • @KerrekSB 迭代器和范围是这些天聊天的主要话题!
  • @KerrekSB 我的内部迭代器都支持 GoF 习惯用法(以及 STL),因此我可以轻松地对它们进行过滤。否则,我会使用 Boost 迭代器,并为增加的复杂性付出代价,因为我通常在标准容器上进行迭代。需要 两个 实例来定义一个范围是一个主要的设计缺陷,但我们必须忍受它。

标签: c++ boost stl boost-range boost-foreach


【解决方案1】:

您可以使用可变参数模板来生成笛卡尔积。下面的代码是基于@zch 的excellent answer 到另一个问题。

#include <tuple>                        // make_tuple, tuple
#include <utility>                      // pair
#include <vector>                       // vector

namespace detail {

// the lambda is fully bound with one element from each of the ranges
template<class Op>
void insert_tuples(Op op)
{
        // evaluating the lambda will insert the currently bound tuple
        op();
}

// "peal off" the first range from the remaining tuple of ranges
template<class Op, class InputIterator1, class... InputIterator2>
void insert_tuples(Op op, std::pair<InputIterator1, InputIterator1> head, std::pair<InputIterator2, InputIterator2>... tail)
{
        // "peal off" the elements from the first of the remaining ranges
        // NOTE: the recursion will effectively generate the multiple nested for-loops
        for (auto it = head.first; it != head.second; ++it) {
                // bind the first free variable in the lambda, and
                // keep one free variable for each of the remaining ranges
                detail::insert_tuples(
                        [=](InputIterator2... elems) mutable { op(it, elems...); },
                        tail...
                );
        }
}

}       // namespace detail

// convert a tuple of ranges to the range of tuples representing the Cartesian product
template<class OutputIterator, class... InputIterator>
void cartesian_product(OutputIterator result, std::pair<InputIterator, InputIterator>... dimensions)
{
        detail::insert_tuples(
                 [=](InputIterator... elems) mutable { *result++ = std::make_tuple(*elems...); },
                 dimensions...
        );
}

你可以这样称呼它:

 int main() 
 {
    bool b[] = { false, true };
    int i[] = { 0, 1 };
    std::string s[] = { "Hello", "World" };

    std::vector< std::tuple<bool, int, std::string> > cp = {
            std::make_tuple(false, 0, "Hello") ,
            std::make_tuple(false, 0, "World"),
            std::make_tuple(false, 1, "Hello"),
            std::make_tuple(false, 1, "World"),
            std::make_tuple(true,  0, "Hello"),
            std::make_tuple(true,  0, "World"),
            std::make_tuple(true,  1, "Hello"),
            std::make_tuple(true,  1, "World")
    };

    std::vector< std::tuple<bool, int, std::string> > result;
    cartesian_product(
            std::back_inserter(result),
            std::make_pair(std::begin(b), std::end(b)),
            std::make_pair(std::begin(i), std::end(i)),
            std::make_pair(std::begin(s), std::end(s))
    );

    std::cout << std::boolalpha << (result==cp) << "\n";

    // now use a single flat loop over result to do your own thing
    for (auto t: result) {
        std::cout << std::get<0>(t) << ", ";
        std::cout << std::get<1>(t) << ", ";
        std::cout << std::get<2>(t) << "\n";
    }
}   

在线output

我对 Boost.Range 不是很熟悉,但您可以轻松地调整这对迭代器以改用 Boost 范围。一个缺点是它不会是增量的,您必须先生成整个笛卡尔积,然后才能对其进行迭代(不过,您问题中的代码似乎不需要break)。

【讨论】:

  • 请注意,您也可以使用detail::insert_tuples(也许有更好的名称)直接迭代叉积而不创建向量。只需将循环体提供为 lambda。
  • @zch 是的,好点,虽然它更像是一个范围 for 循环而不是迭代器(使用 operator++、operator* 和 end() 迭代器)。你会怎么做?根据某些运行时条件中断循环?
  • 是的,问题是关于 for 循环的。有一些方法可以中断,例如使用异常或更改代码以在 lambda 返回 false 时停止,但我同意它们并不完美。
  • A cartesian_iterator 可以由 2 个 std::pair* 参数构造(指向笛卡尔积中第一个和最后一个因子的开始/结束对)。这样的迭代器存储 3 个 N 迭代器的元组:一个用于当前状态,2 个用于笛卡尔积的第一个和最后一个元素。迭代器的 operator* 简单地取消引用元组内的所有迭代器。 operator++ 通过与相应的结束迭代器进行比较,或重置为相应的开始迭代器,从最低有效增加到最高有效。类似于std:next_permutation
猜你喜欢
  • 2012-08-27
  • 2017-09-28
  • 1970-01-01
  • 2011-01-26
  • 2020-11-03
  • 2018-05-20
  • 1970-01-01
  • 2012-01-03
相关资源
最近更新 更多