【问题标题】:Get index in C++11 foreach loop在 C++11 foreach 循环中获取索引
【发布时间】:2014-09-12 22:39:43
【问题描述】:

有没有一种方便的方法可以在 C++11 foreach 循环中获取当前容器条目的索引,比如 python 中的enumerate

for idx, obj in enumerate(container):
    pass

我可以想象一个也可以返回索引或类似的迭代器。

当然我可以有一个计数器,但迭代器通常不保证它们在容器上的迭代顺序。

【问题讨论】:

  • 不,但是将 boost.Range 的 zip 和 boost 的 counting_iterator 一起使用并不难。
  • 复制到某物,答案是自己数。
  • 这个答案怎么样:stackoverflow.com/a/12201788/760746
  • 这听起来不错,但它只适用于顺序容器,不适用于关联容器。对于关联容器,您需要的是键,而不是索引。
  • @EdChum:关联容器的迭代器已经返回键和值。

标签: c++ c++11 foreach


【解决方案1】:

您所要求的功能的良好实现可以在这里找到:

https://github.com/ignatz/pythonic

背后的想法是,您使用自定义迭代器构建一个包装器结构来进行计数。下面是一个非常简单的示例实现来说明这个想法:

// Distributed under the terms of the GPLv2 or newer

#include <iostream>
#include <vector>
#include <tuple>

// Wrapper class
template <typename T>
class enumerate_impl
{
public:
    // The return value of the operator* of the iterator, this
    // is what you will get inside of the for loop
    struct item
    {
        size_t index;
        typename T::value_type & item;
    };
    typedef item value_type;

    // Custom iterator with minimal interface
    struct iterator
    {
        iterator(typename T::iterator _it, size_t counter=0) :
            it(_it), counter(counter)
        {}

        iterator operator++()
        {
            return iterator(++it, ++counter);
        }

        bool operator!=(iterator other)
        {
            return it != other.it;
        }

        typename T::iterator::value_type item()
        {
            return *it;
        }

        value_type operator*()
        {
            return value_type{counter, *it};
        }

        size_t index()
        {
            return counter;
        }

    private:
        typename T::iterator it;
        size_t counter;
    };

    enumerate_impl(T & t) : container(t) {}

    iterator begin()
    {
        return iterator(container.begin());
    }

    iterator end()
    {
        return iterator(container.end());
    }

private:
    T & container;
};

// A templated free function allows you to create the wrapper class
// conveniently 
template <typename T>
enumerate_impl<T> enumerate(T & t)
{
    return enumerate_impl<T>(t);
}



int main()
{
    std::vector<int> data = {523, 1, 3};
    for (auto x : enumerate(data))
    {
        std::cout << x.index << ": " << x.item << std::endl;
    }
}

【讨论】:

  • 警告:信不信由你,如果你点击源链接,这是 GPL 代码。 ... ... 我知道,对吧?我相信 CK1 是(或几年前)github ignatz 的工作合作者。这个答案是在 github 代码之后发布的。 CK1 是否发现它在办公室获得了不同的许可?它是否违反许可证发布在这里?欢迎澄清。
【解决方案2】:

一个简单的解决方案怎么样:

int counter=0;
for (auto &val: container)
{
    makeStuff(val, counter);

    counter++;
}

您可以通过添加范围使在计数器之后添加代码更加“困难”:

int counter=0;
for (auto &val: container)
{{
    makeStuff(val, counter); 
}counter++;}

正如@graham.reeds 所指出的,普通的for 循环也是一种解决方案,它可能会一样快:

int counter=0;
for (auto it=container.begin(); it!=container.end(); ++it, ++counter)
{
    makeStuff(val, counter);
}

最后,另一种使用算法的方法:

int counter = 0;
std::for_each(container.begin(), container.end(), [&counter](int &val){ 
    makeStuff(val, counter++);
});

注意:范围循环和正常循环之间的顺序由标准 6.5.4 保证。这意味着计数器能够与容器中的位置保持一致。

【讨论】:

  • 使用迭代器时,可以使用std::distance(container.begin(), it) 代替计数器变量。
  • @sigalor:根据迭代器的类型,std::distance可能是O(n),不如计数器(O(k))。
  • @AdrianMaire 您的最后一个示例似乎没有增加计数器。
  • 谢谢,现在应该修好了。
  • @sigalor 是的,但这可以说是浪费,特别是如果有很多元素和/或容器是一个迭代算法很昂贵的容器。
【解决方案3】:

如果您可以访问 Boost,则可以像这样使用它的范围适配器:

#include <boost/range/adaptor/indexed.hpp>
using namespace boost::adaptors;
 
for (auto const& elem : container | indexed(0))
{
    std::cout << elem.index() << " - " << elem.value() << '\n';
}

Source(还有其他例子)

【讨论】:

【解决方案4】:

C++17 和结构化绑定使这看起来不错 - 肯定比带有本地 [i = 0](Element&amp;) mutable 的丑陋可变 lambda 或我在承认可能并非所有内容都应该硬塞到 for_each() 之前所做的任何事情要好。 等人。 - 与其他需要在for 循环之外的计数器的解决方案相比。

for (auto [it, end, i] = std::tuple{container.cbegin(), container.cend(), 0};
     it != end; ++it, ++i)
{
      // something that needs both `it` and `i`ndex
}

如果你经常使用这种模式,你可以把它变成通用的:

template <typename Container>
auto
its_and_idx(Container&& container)
{
    using std::begin, std::end;
    return std::tuple{begin(container), end(container), 0};
}

// ...

for (auto [it, end, i] = its_and_idx(foo); it != end; ++it, ++i)
{
    // something
}

C++ 标准提案P2164 建议添加views::enumerate,这将提供一个范围视图,为用户迭代提供元素引用和元素索引。

我们提出了一个视图enumerate,它的值类型是struct,有两个成员indexvalue分别代表适应范围内元素的位置和值。

[ . . .]

此功能以某种形式存在于 Python、Rust、Go(支持该语言)以及许多 C++ 库中:ranges-v3follyboost::ranges (indexed)。

此功能的存在或缺乏是反复出现的 stackoverflow 问题的主题。

嘿,看!我们很有名。

【讨论】:

    【解决方案5】:

    如果您需要索引,那么传统的 for 效果很好。

    for (int idx=0; idx<num; ++idx)
    {
    // do stuff
    }
    

    【讨论】:

    • 这可能很慢。例如链表
    • 可能,但没有提到性能,也没有提到链表。为什么链表上的迭代器会更快?
    • 因为它可以在内部保存当前节点并从那里遍历。不需要随机访问。
    • 因为链表不支持随机访问。它不适用于无序容器,例如setmap
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-18
    • 2020-02-25
    • 2018-04-14
    • 2016-08-21
    • 2021-06-04
    • 2010-11-29
    相关资源
    最近更新 更多