遍历多个向量,在n个元素后执行操作

Iterate over multiple vectors, perform operation after n elements

本文关键字:元素 执行 操作 向量 遍历      更新时间:2023-10-16

我有几个std::vector,我需要一个接一个地迭代它们,以便我可以执行自定义操作,考虑第一组 N 个元素,然后是第二组 N 个元素,依此类推,直到最后一个向量的最后一个元素。

一种可能的方法是将每个向量复制到一个较大的向量中(例如有 2 个输入向量(:

void foo(const vector<int>::iterator& it, const vector<int>::iterator& it2)
{
}
void iterate(const vector<int>& a, const vector<int>& b, int n)
{
    vector<int> c = a;
    c.insert(c.end(), b.begin(), b.end());
    int i, j, len = c.size();
    for (i = 0, j = 0; i < len; i++)
    {
        if (i > 0 && i % n == 0)
        {
            // custom operation from c.begin() + j to c.begin() + i
            foo(c.begin() + j, c.begin() + i);
            j = i;
        }
    }
    if (i % n != 0)
    {
        // custom operation from c.begin() + j to c.end()
        foo(c.begin() + j, c.end());
    }
}

vector<int> a(100), b(50);
iterate(a, b, 32);

但这种方法需要额外的矢量分配,可能是巨大的。有没有办法在不需要额外向量的情况下尽可能快地执行相同的操作?

使用 range-v3,它很简单:

void iterate(const std::vector<int>& a, const std::vector<int>& b, int n)
{
    auto r = ranges::view::concat(a, b) | ranges::view::chunk(n);
    for (const auto& e : r | ranges::view::bounded) {
        foo(e);
    }
}

演示

与 c++17 一起提供 for-range。

您可以使用向量数组,而不是声明单独的向量并简单地迭代它们。

vector<int> v[n];
vector<int>::iterator it;
for(i = 0; i < n; i++) {
   for(it = v[i].begin(); it < v[i].end(); it++) {
      // operation
   }
}

为什么不编写自己的迭代器呢?

迭代器

(前提是您可以将自己限制为所有可能的迭代器操作的子集(只是一个类,您可以在其中提供所需的操作。 在您的情况下,您只需要实现++,==和*。

你可以像这样编写这个迭代器类:

  • 首先编写一个名为 VirtualCollection 的类。
  • VirtualCollection添加一个成员,该成员是一个向量,其中包含指向要循环的基础数据结构的指针(在您的情况下也是向量(。 所有底层数据结构必须具有相同的类型(因此不要循环vector,然后循环list,...(。 让我们将这种底层数据结构称为V,以用于此答案的其余部分。 我们称这个成员为m_collections
  • 添加一个方法addCollection,在其中传递对V 的引用。 Push_back指向m_collectionsV的指针
  • 添加名为 iterator 的内部类。
    • 添加两个类型为 V::iterator(或 V::const_iterator(的成员。 调用一个m_begin和一个m_end(或使用你自己喜欢的命名样式(。
    • 添加类型为 V::iterator 的成员,称为 m_current
  • 将 begin 添加到 VirtualCollection ,这将返回 iterator 内部类的实例。 m_beginm_end 应初始化为 m_collections 中第一个向量的开始和结束。 m_current也应初始化为m_begin
  • 实现iterator的 * 运算符。 它应该简单地返回m_current的内容。
  • 通过将自身传递给方法 VirtualCollection::increment 来实现iterator的 ++ 运算符。 此增量方法将递增m_current 。 如果m_current m_end,则所有 3 个成员都将重新初始化,并在 VirtualCollection 中开始和结束下一个集合。
  • 然后添加一些逻辑来正确处理循环的结束和边界情况(集合为空,... 我把它作为一个练习;-(

您可以构造一个指向输入向量的临时向量,并在循环中对它们进行操作

// not tested
void iterate(const vector<int>& a, const vector<int>& b, int n)
{
    vector<const vector<int> *> c;
    c.push_back(&a);
    c.push_back(&b);
    for(auto vec : c)
    {
        //operate on (*vec), which are a and b
    }
}

不能混合来自两个不同容器的迭代器。因此,当您从第一个容器跳转到第二个容器时,这将不起作用。

如果您主要关心的是分配额外的向量,则可以就地工作,并且仅在从第一个向量到第二个向量时分配临时向量。这将减少额外的内存要求以n

void iterate(const std::vector<int> &a, const std::vector<int> &b, int n)
{
    int remain = a.size();
    for (int i = 0; remain >= n; remain -= n, i += n) {
        foo(a.begin() + i, a.begin() + i + n);
    }
    if (remain > 0) {
        std::vector<int> tmp;
        tmp.insert(tmp.end(), a.end() - remain, a.end());
        tmp.insert(tmp.end(), b.begin(), b.begin() + n - remain);
        foo(tmp.begin(), tmp.end());
    }
    int start = n - remain;
    remain = b.size() - start;
    for (int i = start; remain >= n; remain -= n, i += n) {
        foo(b.begin() + i, b.begin() + i + n);
    }
    if (remain > 0)
        foo(b.end() - remain, b.end());
}

如果你不关心每次都精确地做n元素,你也可以只处理一个向量及其其余部分,然后处理下一个向量

void iterate(const std::vector<int> &v, int n)
{
    int remain = v.size();
    for (int i = 0; remain >= n; remain -= n, i += n)
        foo(v.begin() + i, v.begin() + i + n);
    if (remain > 0)
        foo(v.end() - remain, v.end());
}
std::vector<int> a(100), b(50);
iterate(a, 32);
iterate(b, 32);

你可以像这样使用 std::reference_wrapper:

static int val = 1;
void foo(const vector<int>::iterator& it, const vector<int>::iterator& it2)
{
    std::for_each(it, it2, [](int& v) {v = val; });
    val++;
}
void iterate(const vector<std::reference_wrapper<vector<int>>>& data, vector<int>::size_type n)
{
    vector<int>::size_type tmp_beg = 0;
    vector<int>::size_type tmp_end = n;
    for (std::reference_wrapper<vector<int>> vref : data)
    {
        vector<int>& v = vref.get();
        while (tmp_end < v.size())
        {
            foo(v.begin() + tmp_beg, v.begin() + tmp_end);
            tmp_beg = tmp_end;
            if (tmp_beg >= v.size()) {
                tmp_beg -= v.size();
            }
            tmp_end = tmp_beg + n;
        } //tmp_end >= v.size()
        foo(v.begin() + tmp_beg, v.end());
        val--; //We need the same value again....
        tmp_beg = 0;
        tmp_end -= v.size();
        if (tmp_end == 0) {
            tmp_end = n;
        }
    }
}

int main()
{
    vector<int> a(100), b(50);
    iterate({ a,b }, 32);
    return 0;
}