什么是同时迭代两个或更多容器的最佳方法

mem*_*ecs 98 c++ containers iterator c++11

C++ 11提供了多种迭代容器的方法.例如:

基于范围的循环

for(auto c : container) fun(c)
Run Code Online (Sandbox Code Playgroud)

的std :: for_each的

for_each(container.begin(),container.end(),fun)
Run Code Online (Sandbox Code Playgroud)

但是,建议的方法是迭代两个(或更多)相同大小的容器来完成以下操作:

for(unsigned i = 0; i < containerA.size(); ++i) {
  containerA[i] = containerB[i];
}
Run Code Online (Sandbox Code Playgroud)

Kon*_*lph 47

聚会迟到了.但是:我会迭代索引.但不是经典for循环,而是for在索引上使用基于范围的循环:

for(unsigned i : indices(containerA)) {
    containerA[i] = containerB[i];
}
Run Code Online (Sandbox Code Playgroud)

indices是一个简单的包装函数,它返回索引的(延迟评估的)范围.由于实现 - 虽然简单 - 在这里发布它有点太长了,你可以在GitHub上找到一个实现.

此代码与使用手动,经典循环一样高效for.

如果您的数据中经常出现此模式,请考虑使用另一个模式,该模式包含zip两个序列并生成一系列元组,对应于配对元素:

for (auto& [a, b] : zip(containerA, containerB)) {
    a = b;
}
Run Code Online (Sandbox Code Playgroud)

实现zip是留给读者的练习,但它的实现很容易indices.

(在C++ 17之前,您必须编写以下代码:)

for (auto items&& : zip(containerA, containerB))
    get<0>(items) = get<1>(items);
Run Code Online (Sandbox Code Playgroud)

  • @SebastianK在这种情况下最大的区别是语法:我的(我声称)在这种情况下客观上更好用.此外,您可以指定步长.有关示例,请参阅链接的Github页面,特别是README文件. (3认同)
  • 与boost counting_range相比,您的索引实现有什么优势吗?人们可以简单地使用`boost :: counting_range(size_t(0),containerA.size())` (2认同)

Xeo*_*Xeo 37

对于您的具体示例,只需使用

std::copy_n(contB.begin(), contA.size(), contA.begin())
Run Code Online (Sandbox Code Playgroud)

对于更一般的情况,您可以使用Boost.Iterator zip_iterator,使用一个小函数使其可用于基于范围的for循环.在大多数情况下,这将有效:

template<class... Conts>
auto zip_range(Conts&... conts)
  -> decltype(boost::make_iterator_range(
  boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
  boost::make_zip_iterator(boost::make_tuple(conts.end()...))))
{
  return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
          boost::make_zip_iterator(boost::make_tuple(conts.end()...))};
}

// ...
for(auto&& t : zip_range(contA, contB))
  std::cout << t.get<0>() << " : " << t.get<1>() << "\n";
Run Code Online (Sandbox Code Playgroud)

实例.

然而,对于全面的通用性,你可能想要的东西更像是这样,这将正常工作,数组和用户定义的类型没有成员begin()/ end()这样做begin/ end功能在他们的命名空间.此外,这将允许用户专门const通过这些zip_c...功能进行访问.

如果你是像我这样的好错误消息的拥护者,那么你可能想要这个,它检查是否有任何临时容器传递给任何zip_...函数,如果是这样就打印一个很好的错误消息.

  • @Xeo这些示例的所有链接都被破坏了. (21认同)

Jos*_*eph 30

我想知道为什么没有人提到这个:

auto ItA = VectorA.begin();
auto ItB = VectorB.begin();

while(ItA != VectorA.end() || ItB != VectorB.end())
{
    if(ItA != VectorA.end())
    {
        ++ItA;
    }
    if(ItB != VectorB.end())
    {
        ++ItB;
    }
}
Run Code Online (Sandbox Code Playgroud)

PS:如果容器大小不匹配,那么你必须将代码放在if语句中.


Wil*_*ell 20

答案就在这里!...当 C++23 到来时。

#include <algorithm>
#include <forward_list>
#include <ranges>
#include <array>
#include <iostream>

int main()
{
    auto foos = std::to_array({ 1, 2, 3, 4, 5  });
    auto woos = std::to_array({ 6, 7, 8, 9, 10 });

    auto fooswoos = std::views::zip(foos,woos);

    for(auto [foo, woo] : fooswoos) {
        woo += foo;
    }
    std::ranges::for_each(woos, [](const auto& e) { std::cout << e << '\n'; });

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

那么,发生了什么事?

我们正在构建一个特殊的“视图”。这种视图使我们能够将容器视为其他结构,而无需实际进行任何复制或类似操作。使用结构化绑定,我们能够在每次迭代时引用每个对齐元素,并执行我们想要的任何操作(并且安全地)

立即在编译器资源管理器上检查一下!


wjl*_*wjl 9

有很多方法可以使用标题中提供的多个容器来执行特定操作algorithm.例如,在您给出的示例中,您可以使用std::copy而不是显式的for循环.

另一方面,除了普通for循环之外,没有任何内置方法可以一般地迭代多个容器.这并不奇怪,因为有很多方法可以迭代.想一想:你可以用一步迭代一个容器,用另一个步骤迭代一个容器; 或者通过一个容器直到它到达终点然后在你进入另一个容器的末端时开始插入; 或者每次完全通过另一个容器时第一个容器的一步然后重新开始; 或其他一些模式; 或一次超过两个容器; 等......

但是,如果你想制作自己的 "for_each"样式函数,它只迭代两个容器,只能达到最短容器的长度,你可以这样做:

template <typename Container1, typename Container2>
void custom_for_each(
  Container1 &c1,
  Container2 &c2,
  std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f)
{
  Container1::iterator begin1 = c1.begin();
  Container2::iterator begin2 = c2.begin();
  Container1::iterator end1 = c1.end();
  Container2::iterator end2 = c2.end();
  Container1::iterator i1;
  Container1::iterator i2;
  for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
    f(i1, i2);
  }
}
Run Code Online (Sandbox Code Playgroud)

显然,您可以以类似的方式制定任何类型的迭代策略.

当然,您可能会争辩说直接执行内部for循环比编写这样的自定义函数更容易......如果您只打算执行一次或两次,那么您是对的.但好的是,这是非常可重用的.=)


小智 8

如果您只需要同时迭代2个容器,则在boost范围库中有一个扩展版本的标准for_each算法,例如:

#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>

void foo(int a, int& b)
{
    b = a + 1;
}

int main()
{
    std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
    std::vector<int> contB(contA.size(), 0);

    boost::for_each(contA, contB, boost::bind(&foo, _1, _2));
    // contB will be now 5,4,6,3
    //...
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当您需要在一个算法中处理超过2个容器时,您需要使用zip.


poo*_*a13 8

如果可能的话,我个人更喜欢使用 STL 中已有的内容(在<algorithm>标头中)。std::transform有一个可以接受两个输入迭代器的签名。因此,至少对于两个输入容器的情况,您可以这样做:

std::transform(containerA.begin(), containerA.end(), containerB.begin(), outputContainer.begin(), [&](const auto& first, const auto& second){
    return do_operation(first, second);
});
Run Code Online (Sandbox Code Playgroud)

请注意,outputContainer也可以是输入容器之一。但一个限制是,如果您要修改其中一个容器,则无法执行更新后操作。


Vah*_*hid 6

另一种解决方案可能是在 lambda 中捕获另一个容器的迭代器的引用,并在其上使用后增量运算符。例如简单的副本将是:

vector<double> a{1, 2, 3};
vector<double> b(3);

auto ita = a.begin();
for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })
Run Code Online (Sandbox Code Playgroud)

在 lambda 中,你可以做任何事情,ita然后增加它。这很容易扩展到多个容器的情况。


Jen*_*ens 6

范围库提供了这个功能和其他非常有用的功能。以下示例使用Boost.RangeEric Niebler 的 rangev3应该是一个不错的选择。

#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& i: boost::combine(v, l))
    {
        int ti;
        char tc;
        boost::tie(ti,tc) = i;
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

C++17 将通过结构化绑定使这一点变得更好:

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& [ti, tc]: boost::combine(v, l))
    {
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)


Szy*_*zak 5

我也来晚了一点;但你可以使用这个(C 风格的可变参数函数):

template<typename T>
void foreach(std::function<void(T)> callback, int count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; i++) {
        std::vector<T> v = va_arg(args, std::vector<T>);
        std::for_each(v.begin(), v.end(), callback);
    }

    va_end(args);
}

foreach<int>([](const int &i) {
    // do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);
Run Code Online (Sandbox Code Playgroud)

或这个(使用函数参数包):

template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
    std::for_each(v.begin(), v.end(), callback);
}

template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &v, Args... args) {
    std::for_each(v.begin(), v.end(), callback);
    return foreach(callback, args...);
}

foreach([](const int &i){
    // do something here
}, vecA, vecB, vecC, vecD, vecE, vecF);
Run Code Online (Sandbox Code Playgroud)

或这个(使用大括号括起来的初始值设定项列表):

template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> list) {
    for (auto &vec : list) {
        std::for_each(vec.begin(), vec.end(), callback);
    }
}

foreach([](const int &i){
    // do something here
}, {vecA, vecB, vecC, vecD, vecE, vecF});
Run Code Online (Sandbox Code Playgroud)

或者您可以像这里一样连接向量:What is the best way to concatenate Two vector? 然后迭代大向量。