【问题标题】:How to slice a vector in c++ and assign to itself?如何在 C++ 中对向量进行切片并分配给自身?
【发布时间】:2016-10-01 04:55:39
【问题描述】:

我想知道如何在不重新分配新向量的情况下用该向量的子集替换向量的内容(我发现的每个问题似乎都有分配新向量的目的)

示例:

vector<int>* testVec = new vector<int>{1, 2, 3, 4, 5};
//do some operation to slice vector, to say {2, 3, 4} without re-allocating
//testVec should now have contents {2, 3, 4}

【问题讨论】:

  • 看看 eric nieblers 的范围和视图:github.com/ericniebler/range-v3 可能是你想要的。
  • 擦除第一个和最后一个元素?
  • 不要像这样使用new,呃。 :(
  • 请注意,并非所有解决方案都是等效的,以防引发异常,这可能会影响您应该使用哪个解决方案。
  • 顺便说一句,当你有外部分配时,想要避免额外的内部分配没有多大意义......你没有进行微不足道的优化并且想做可能无用的优化。

标签: c++ c++11 visual-c++ vector subset


【解决方案1】:

关于使用范围的问题尚不清楚,但对std::vector::erase 的两次调用就足够了,并且不会导致任何重新分配。

你的例子:

std::vector<int> testVec{1, 2, 3, 4, 5};
testVec.erase(testVec.begin() + 4, testVec.end());
testVec.erase(testVec.begin(), testVec.begin() + 1);

通过首先删除向量的尾部,您可以确保值只移动一次。

Here is the full code.

请注意,我假设您实际上不需要使用指向 std::vector 的指针。

【讨论】:

  • 最后擦除可以通过使用小于当前大小的参数调用resize来实现。在这种情况下resize(4)
  • @Revolver_Ocelot 但这会导致重新分配和移动/复制剩余的元素。 ;-)
  • 不,它不会重新分配,因为它会使迭代器无效,并且只有在容量发生变化时才能这样做(使用大于当前容量的参数调用调整大小)。
  • @Revolver_Ocelot 再次阅读文档后,看来您是对的。感谢您指出。
【解决方案2】:

一种简单的方法是使用读/写方法:

int wp = 0; // "write ptr"
int rp = 0; // "read ptr"
for (auto& x : v) {
    if (... i want to keep element x, index rp ...) {
      if (rp != wp) v[wp] = std::move(x);
      wp++;
    }
    rp++;
}
v.resize(wp);

这种方案也适用于过滤依赖于索引和元素值的条件。

【讨论】:

    【解决方案3】:

    您可以使用std::rotatevector::erase 的组合

    void slice(vector<int> & v, std::size_t start, std::size_t size)
    {
        assert( v.size() > start + size );
        std::rotate( v.begin(), v.begin() + start, v.end() );
        // Vector is now {2,3,4,5,1}
        v.erase( v.begin() + size, v.end() );
    }
    

    【讨论】:

    • 或者,在旋转之后,您可以只使用resize 来减小大小。
    • 转念一想,这不是一个特别有效的解决方案。 rotate 将复制/移动将在下一行删除的元素。
    【解决方案4】:

    只需删除您不想要的部分:

    std::vector<int> vec{1, 2, 3, 4, 5};
    vec.erase(vec.begin() + 4, vec.end());
    vec.erase(vec.begin(), vec.begin() + 1);
    

    【讨论】:

      【解决方案5】:

      这是一个带有谓词的小函数。

      对于传递谓词的容器中的每个元素,该元素都会从容器中删除。

      没有重新分配,每个元素最多std::move'd一次:

      template<class C, class F>
      void erase_if( C& c, F&& f ) {
        using std::begin; using std::end;
        auto it = std::remove( begin(c), end(c), std::forward<F>(f) );
        c.erase( it, end(c) );
      }
      

      使用索引执行此操作有点棘手。如果索引是连续的,您可以将第一个旋转到第一个位置,然后擦除容器的尾部,或者擦除尾部和前端,或者多种方式。

      如果不是,您基本上必须编写std::remove 的手动版本,它适用于索引而不是元素值。

      如果你有一个“计数”测试函数来计算它被调用的元素,它可能会起作用,但是依赖于调用测试函数的顺序似乎过于脆弱。 remove_by_index 很简单:

      template<class Begin, class End, class Test>
      void remove_by_index(Begin b, End e, Test t) {
        auto writer = b;
        auto reader = b;
        std::size_t index = 0;
        while (reader != e) {
          if (t(index)) {
            ++reader; ++index;
            continue;
          }
          if (reader != writer) {
            *writer = std::move(*reader);
          }
          ++reader; ++writer; ++index;
          continue;
        }
        return writer;
      }
      

      给我们:

      template<class C, class F>
      void erase_by_index( C& c, F&& f ) {
        using std::begin; using std::end;
        auto it = remove_by_index( begin(c), end(c), std::forward<F>(f) );
        c.erase( it, end(c) );
      }
      

      假设你想保留所有偶数位置元素的切片:

      erase_by_index( vec, [](auto i){return i&1;} );
      

      或者假设我们想要保持一个区间:

      template<class C>
      void keep_interval( C& c, std::size_t start_index, std::size_t length ) {
        erase_by_index(c, [=](auto i){ return i < start_index || i >= (start_index+length); } );
      }
      

      现在更通用的方法是创建容器的替代视图,其中视图中的每个元素对应于原始容器中的某些元素范围,在该视图上进行测试,然后将操作应用回原始容器容器。

      不知道如何简洁。

      【讨论】:

        猜你喜欢
        • 2018-11-06
        • 2019-11-02
        • 1970-01-01
        • 2017-01-02
        • 1970-01-01
        • 2010-12-17
        • 1970-01-01
        • 2022-01-25
        相关资源
        最近更新 更多