C++ 中展开嵌套循环的迭代器

问题描述 投票:0回答:1

我经常有至少两层深度的嵌套循环,其中来自同一容器的元素彼此交互。想象一下物理学中粒子的相互作用。

这导致了代码的两种情况:

struct vec2f {
    float x, y;
};

void each_to_each(std::vector<vec2f>& vec)
{
    std::for_each(std::begin(vec), std::end(vec), [&]([[maybe_unused]] const auto& pos1) {
        const auto it = std::begin(vec) + (&pos1 - std::data(vec));
        std::for_each(it + 1, std::end(vec), [&]([[maybe_unused]] const auto& pos2) {
            // Some actions with pos1 and pos2 (like particles interaction)
        });
    });
}

void each_to_each_symmetrical(std::vector<vec2f>& vec)
{
    std::for_each(std::begin(vec), std::end(vec), [&]([[maybe_unused]] const auto& pos1) {
        std::for_each(std::begin(vec), std::end(vec), [&]([[maybe_unused]] const auto& pos2) {
            if (&pos1 == &pos2) {
                return;
            }

            // Some actions with pos1 and pos2 (like particles interaction)
        });
    });
}

我不喜欢过于冗长的语法、难以退出内循环、嵌套括号等。

因此,避免这种情况的典型方法是将这些循环放入包装器中:

template<typename Functor>
void each_to_each_with_functor(std::vector<vec2f>& vec, Functor fn)
{
    std::for_each(std::begin(vec), std::end(vec), [&](const auto& pos1) {
        const auto it = std::begin(vec) + (&pos1 - std::data(vec));
        std::for_each(it + 1, std::end(vec), [&](const auto& pos2) {
            fn(pos1, pos2);
            });
        });
}

void foo([[maybe_unused]]const vec2f& pos1, [[maybe_unused]] const vec2f& pos2) {
    // Some actions with pos1 and pos2 (like particles interaction)
}

这工作正常,但保留了退出嵌套循环的问题,并且另外带来了将上下文(循环之前定义的局部变量)传递到函数中的问题;当它们很多时,这就太冗长了。有时,性能也可能是一个问题。

我的想法是实现一种迭代器和包装器,它将封装嵌套循环的处理(带有一对迭代器),并且能够为开发人员在单级循环中工作。

所以用法应该是这样的:

    std::vector<vec2f> vec;
    NestedLoopIterator loop = NestedLoopIterator(vec.begin(),vec.end());

    std::for_each(loop.begin(), loop.end(), [&](const auto& its) {
        const vec2f& pos1 = its.it1;
        const vec2f& pos2 = its.it2;

        // Some actions with pos1 and pos2 (like particles interaction)
    });

它可以定义遍历策略(如问题开头所示的两个策略),处理多线程,可以配置嵌套级别等。

为了避免重新发明轮子,是否有此类迭代器的已知实现?

如果没有,并且您发现此类解决方案存在重大缺陷以及拟议的设计草案中存在缺陷,请分享您的意见。如果这是一个好主意,并且其好处会超过引入的复杂性,我仍然有两种想法。

这是迄今为止尚未实现 NestedLoopIterator

演示

c++ loops iterator std nested-loops
1个回答
0
投票

您可以使用 range-v3 库实现这些类型的迭代。 然而,我认为这些范围不会表现出可能的最佳性能。我不是范围库方面的专家,只是一种直觉,我通过查看代码并看到所有这些额外的东西被创建而得到的。

这是使用 range-v3 库进行迭代的简单实现。 请注意,

each_to_each_symmetrical
迭代根本不需要有 2 个循环(仅基于上面的示例,如果我错了,请纠正我),因此该示例中没有范围。但如果你坚持在那里进行某种“双重迭代”,
range-v3
库已经实现了
ranges::views::zip

#include <algorithm>
#include <iostream>
#include <range/v3/all.hpp>

int main() {
    std::cout << "each_to_each case\n";

    std::vector<int> vec{1, 2, 3, 4};
    auto rng = ranges::views::cartesian_product(ranges::views::enumerate(vec),
                                                ranges::views::enumerate(vec)) |
               ranges::views::filter([](const auto& entry) {
                   const auto& [l, r] = entry;
                   return l.first < r.first;
               }) |
               ranges::views::transform(
                   [](const auto& entry) -> std::pair<const int&, const int&> {
                       const auto& [l, r] = entry;
                       return {l.second, r.second};
                   });

    std::ranges::for_each(
        rng, [](const std::pair<const int&, const int&> entry) {
            std::cout << "(" << entry.first << " ; " << entry.second << ")\n";
        });

    // Output:
    // (1 ; 2)
    // (1 ; 3)
    // (1 ; 4)
    // (2 ; 3)
    // (2 ; 4)
    // (3 ; 4)

    std::cout << "each_to_each_symmetrical case\n";
    std::for_each(vec.begin(), vec.end(), [](const auto& el) {
        std::cout << "(" << el << " ; " << el << ")\n";
    });

    // Output:
    // (1 ; 1)
    // (2 ; 2)
    // (3 ; 3)
    // (4 ; 4)

    return 0;
}

您可以使用不同类型的视图来实现相同的事情。您可以阅读有关视图的更多信息这里

© www.soinside.com 2019 - 2024. All rights reserved.