如何最好地控制迭代方向

How best to control iteration direction?

本文关键字:迭代 方向 控制 何最好      更新时间:2023-10-16

我有一个大型对象的容器,这些对象的复制成本很高。有时我必须正常地对整个容器进行迭代,有时则相反。一旦我确定了迭代方向,我就不需要在飞行途中进行更改,即不需要随机访问。

我希望能做这样的事情:

#include <iostream>
#include <vector>
using namespace std;
int main( int argc, char** )
{
    // pretend this is a vector of expensive objects
    vector<int> foo = {1,2,3,4,5};
    // calculate forward or backward iteration direction
    bool backwards = (argc > 1);
    if( backwards )
        // prepare backward iteration, but don't copy objects
    else
        // prepare forward iteration, but don't copy objects
    for( auto& i : /* either forward or backward */ )
    {
        // my loop body
        cout << i;
    }
    return 0;
}

这是一个C++11程序,但我认为这对我没有真正的帮助。我只是没有找到最好的方法。谢谢你的帮助。

C++标准容器附带了这些称为"反向迭代器"的东西。使用std::vector::rbegin()std::vector::rend()来获得一个迭代器,该迭代器通过向量进行反向迭代。C++03可以很容易地做到这一点:

#include <iostream> 
#include <vector>  
// Use const reference to pass expensive-to-copy types
void loop_body(const int& i)
{
    std::cout << i;
}
int main( int argc, char** ) 
{ 
    // pretend this is a vector of expensive objects 
    std::vector<int> foo = {1,2,3,4,5}; 
    // calculate forward or backward iteration direction 
    bool backwards = (argc > 1); 
    if( backwards ) { 
        std::for_each(foo.rbegin(), foo.rend(), &loop_body);
    } else { 
        std::for_each(foo.begin(), foo.end(), &loop_body);
    } 
    return 0; 
} 

您可以在C++11:中使用lambdas来实现这一点

#include <iostream> 
#include <vector> 
int main( int argc, char** ) 
{ 
    // pretend this is a vector of expensive objects 
    std::vector<int> foo = {1,2,3,4,5}; 
    // calculate forward or backward iteration direction 
    bool backwards = (argc > 1); 
    // Use const reference to pass expensive-to-copy types
    auto loop_body = [](const int& i)
    {
        std::cout << i;
    };
    if( backwards ) { 
        std::for_each(foo.rbegin(), foo.rend(), loop_body);
    } else { 
        std::for_each(foo.begin(), foo.end(), loop_body);
    } 
    return 0; 
}

为什么不将算法放入模板函数中?然后用begin/end或rbegin/rend来称呼它是微不足道的。

template <class Iterator>
void do_stuff(Iterator first, Iterator last)
{
    // Your loop code here.
}

或者您可以使用lambda(因为它是C++11)和std::for_each作为:

auto loop_body = [&](int &i) { std::cout << i << std::endl; } ;
if (backward)
  std::for_each(v.rbegin(), v.rend(), loop_body);
else
  std::for_each(v.begin(), v.end(), loop_body);

标准库容器同时具有正常迭代器和反向迭代器,这解决了大部分问题。

不幸的是,它们是不同的类型,因此您无法创建一个可以容纳正常迭代器或反向迭代器的变量。

所以我要做的是将你的循环包装在一个单独的函数中,并将其模板化以同时使用:

template <typename It>
void myloop(It first, It last) {
    for(It cur = first; cur != last; ++cur)
    {
        // my loop body
        cout << *cur;
    }
}

然后这样称呼它:

if( backwards )
    myloop(foo.rbegin(), foo.rend());
else
    myloop(foo.begin(), foo.end());

当然,那么你可能也可以使用一种标准的库算法来代替你的循环:

if( backwards )
    std::for_each(foo.rbegin(), foo.rend(), [](int item){  cout << item;});
else
    std::for_each(foo.begin(), foo.end(), [](int item){  cout << item;});

(注意,为了简单起见,我在这里使用for_each。很可能,std::transformstd::copy更适合描述你想做的事情。

我还使用了lambda表达式,而不是myloop函数。你可以做任何一种,但lambda要短得多,IMO更容易阅读。

std::vector<int>::iterator begin = foo.begin();
std::vector<int>::iterator last = foo.end();
if (last != begin)
{
    --last;
    int direction = 1;
    if( backwards )
    {
        std::swap(begin, last);
        direction = -1;
    }
    for( auto& i = begin;  ; i += direction)
    {
        // do stuff
        if (i == last)
            break;
    }
}

即使这是一个老问题,我最近也遇到了同样的问题,并以这种方式解决了它:

把这个放在某个地方:

namespace mani {
    // an extension to std::for_each where you can choose the direction:
    template <class InputIterator, class Function> Function for_each_reverse(bool reverse, const InputIterator& first, const InputIterator& last, Function fn)
    {
        if (reverse) {
            return std::for_each(std::reverse_iterator<InputIterator>(last), std::reverse_iterator<InputIterator>(first), fn);
        }
        return std::for_each(first, last, fn);
    }
}

那么你可以简单地使用它:

auto start = ...
auto end = ...
bool reverse = ...
mani::for_each_reverse(reverse, start, end, [&](const MyItem& item) {
    ...
});

如果reverse为false,则它将在正常方向上对项进行迭代。如果reverse为true,则它将在项目上向后迭代。

您需要的是制作自己的迭代器包装器。我能想到三种方法:

  • 一个类似于两种迭代器类型的union;它有两个不同类型的迭代器成员,以及一个选择哪一个是活动的标志。

  • 一个包含单个双向迭代器和指示是正向操作还是反向操作的标志的迭代器。

  • 某种带有虚拟函数的通用迭代器。(效率低下,但写起来可能很简单)