在递增整数索引的同时迭代容器的惯用方法是什么?

Rei*_*ica 31 c++ iteration stl c++11

假设您想要在迭代不提供随机访问迭代器的容器时知道元素的数字索引.例如:

std::list<std::string> items;
int i = 0;
for (auto & item : items) item += std::to_string(i++);
Run Code Online (Sandbox Code Playgroud)

是否有更惯用或更好的方式来做到这一点?我认为这种模式会出现在各种情况下.我不喜欢在循环之外可用的整数索引.包围循环和本地块中的索引定义似乎也很难看.

当然,当容器提供随机访问迭代器时,可以利用迭代器差异,但是你不能使用range-for:

std::vector<std::string> items;
for (auto it = items.begin(); it != items.end(); ++it)
  *it += std::to_string(it - items.begin());
Run Code Online (Sandbox Code Playgroud)

虽然我只展示了C++ 11的例子,但我也在寻找C++ 0x和C++ 98的提示.

Tem*_*Rex 30

我的个人偏好:只需保留额外的索引.它很明显,如果你有一个if()内部循环,你也可以轻松跳过计数:

std::list<std::string> items;
{
    int i = 0;
    for (auto & item : items) {
        if (some_condition(item)) {
            item += std::to_string(i); // mark item as the i-th match
            ++i;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

只需确保将i计数器靠近循环,使用extra { }来创建嵌套范围.此外,后增量是边界不清楚.

替代方案:我想要一个基于范围的index_for语言构造,它将提供一个自动计数器i,但唉,目前情况并非如此.

但是,如果你绝对,肯定地,明确地坚持一些不错的包装器,那么查看循环的语义实际上是有益的,这是std::transform一对std::list迭代器和一个迭代器的语义boost::counting_iterator.

std::transform(
    begin(items), end(items), 
    boost::counting_iterator<int>(0), 
    begin(items), 
    [](auto const& elem, auto i) {
    return elem + std::to_string(i);    
});
Run Code Online (Sandbox Code Playgroud)

这4条腿的过载std::transform有时被称为zip_with,这就是为什么有一些意见,使用boost::zip_iteratorlistcounting_iterator.

您可以使一些不错的基于范围的包装器更简洁:

template<class Range, class T, class BinaryOp>
void self_transform(Range& rng, T value, BinaryOp op)
{
    auto i = value;
    for (auto& elem : rng) {
        elem = op(elem, i);        
        ++i;
    }
}
Run Code Online (Sandbox Code Playgroud)

可以更紧凑地称为:

self_transform(items, 0, [](auto const& elem, auto i) {
    return elem + std::to_string(i);    
});
Run Code Online (Sandbox Code Playgroud)

实例.

  • @KubaOber你想要所有你想要的东西,但是循环中的任何`i`对于任何读者都是立即清楚的,而一些`std :: distance`或zip迭代器是不必要的混淆. (13认同)
  • 如果你非常想要那个徽章,我可以删除upvote:P (3认同)

jro*_*rok 10

有些编译器已经提供了带有lambda捕获的表达式,这些表达式将采用C++ 1y标准.所以你可以这样做:

#include <string>
#include <list>
#include <iostream>

int main()
{
    std::list<std::string> items {"a","b","c","d","e"};

    //                 interesting part here, initializes member i with 0, 
    //          ?????? type being deduced from initializer expression            
    auto func = [i = 0](auto& s) mutable { s+= std::to_string(i++); };
    for (auto& item : items) func(item);

    for (const auto& item : items) std::cout << item << ' ';
}
Run Code Online (Sandbox Code Playgroud)

输出: a0 b1 c2 d3 e4

编辑:为了记录,我认为在循环范围之外有一个小索引变量是最好的(见其他答案).但为了好玩,我编写了一个迭代器适配器(在Boost Iterator Adapter的帮助下),您可以使用它将成员函数绑定index到任何迭代器:

#include <boost/iterator/iterator_adaptor.hpp>
#include <list>
#include <string>
#include <iostream>
#include <algorithm>

// boiler-plate

template<typename Iterator>
class indexed_iterator
: public boost::iterator_adaptor<indexed_iterator<Iterator>, Iterator>
{
public:
    indexed_iterator(Iterator it, int index_value = 0)
    : indexed_iterator::iterator_adaptor_(it)
    , i_(index_value)
    { }

private:
    int i_;

    friend class boost::iterator_core_access;
    void increment(){ ++i_; ++this->base_reference(); }

    /* TODO: decrement, etc. */

public:
    int index() const { return i_; }
};

template<typename Iterator>
indexed_iterator<Iterator> make_indexed_iterator(Iterator it, int index = 0)
{
    return indexed_iterator<Iterator>(it, index);
}

// usuable code

int main()
{
    std::list<std::string> items(10);

    auto first = make_indexed_iterator(items.begin());
    auto last  = make_indexed_iterator(items.end());
    while (first != last) {
        std::cout << first.index() << ' ';
        ++first;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出: 0 1 2 3 4 5 6 7 8 9

  • 啊,那个小小的`func(item)`来电.看起来很无辜,但却改变了`func`和`item`.我希望这不会成为惯用语...... (4认同)

Joh*_*eek 6

我可能最终得到这样的东西:

std::list<std::string> items = ...;

{
    int index = 0;
    auto it = items.begin();
    for (; it != items.end(); ++index, ++it)
    {
        *it += std::to_string(index);
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经看到更多使用带有两个循环变量的for循环,而不是使用zipped迭代器或lambda捕获计数变量."惯用语"是一个主观的术语,但我称之为惯用语.

有一个明确的额外变量使我们显然只是向上计数.如果您决定在循环中执行任何非平凡的操作,这一点很重要.例如,您可以在列表中插入或删除项目并相应地调整索引 - 如果您使用的是迭代器适配器,则它提供的索引实际上可能实际上并不是容器中项目的索引.


或者,您可以编写以下变体std::for_each:

template <typename InputIt, typename BinaryFn>
BinaryFn for_each_index(InputIt first, InputIt last, BinaryFn fn)
{
    for (int i = 0; first != last; ++i, ++first)
    {
        fn(i, *first);
    }
    return fn;
}
Run Code Online (Sandbox Code Playgroud)

至少没有混淆.然后你可以这样做:

std::list<std::string> items = ...;
for_each_index(items.begin(), items.end(), [](int i, std::string& s)
{
    s += std::to_string(i);
});
Run Code Online (Sandbox Code Playgroud)


Pau*_* II 5

使用Boost.Range你可以这样做:

std::list<std::string> items;
for (const auto & t : boost::combine(items, boost::irange(0, items.size()))) 
{
    std::string& item = boost::get<0>(t);
    int i = boost::get<1>(t);
    item += std::to_string(i);
}
Run Code Online (Sandbox Code Playgroud)