【问题标题】:Best way to extract a subvector from a vector?从向量中提取子向量的最佳方法?
【发布时间】:2010-09-30 02:40:30
【问题描述】:

假设我有一个大小为Nstd::vector(我们称之为myVec)。构造一个由元素 X 到 Y 的副本组成的新向量的最简单方法是什么,其中 0 myVec [100000] 到 myVec [100999] 在大小为 150000 的向量中。

如果这不能用向量有效地完成,我应该使用另一种 STL 数据类型吗?

【问题讨论】:

  • 你说你想提取一个子向量,但在我看来,你真正想要的是一个视图/访问子向量 - 区别在于视图不会复制 - 老派 C++要使用开始指针和结束指针,鉴于 std::vector 上的 mem 是连续的,那么您应该可以使用指针进行迭代,从而避免复制,但是如果您不介意复制,那么只需初始化一个具有先前向量范围的新向量
  • 从 c++11 开始就有 .data()(cplusplus.com/reference/vector/vector/data)。但是,不鼓励在 stl 容器中使用指针,请参阅 stackoverflow.com/questions/31663770/…
  • @serup 可能对 OP 不感兴趣,但我需要知道如何“用你以前的向量的范围初始化一个新向量”。

标签: c++ stl vector range


【解决方案1】:
vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
vector<T> newVec(first, last);

构造新向量需要 O(N) 运算,但实际上没有更好的方法。

【讨论】:

  • +1,也是 O(Y-X),小于或等于 O(N)(在他的例子中更小)
  • @orip 好吧,毕竟是 O(N)。
  • @GregRogers:在 N 是特定数字的情况下使用大 O 表示法没有意义。 Big-O 传达关于 N 如何变化的增长率。 Johann:最好不要以两种方式使用一个变量名。我们通常会说O(Y-X),或者我们会说O(Z) where Z=Y-X
  • @GregRogers 通过这种方式,我们需要声明一个新的向量。有没有办法改变原始向量?像 myVec(第一个,最后一个)?我知道这是错误的,但我真的需要解决方案,因为我想在我的代码中使用递归,并且需要重复使用相同的向量(尽管已更改)。谢谢!
  • 为什么不只是vector&lt;T&gt; newVec(myVec.begin() + 100000, myVec.begin() + 101000);
【解决方案2】:

只需使用向量构造函数。

std::vector<int>   data();
// Load Z elements into data so that Z > Y > X

std::vector<int>   sub(&data[100000],&data[101000]);

【讨论】:

  • 好吧,我没有意识到从任意向量元素获取迭代器这么简单。
  • 获取这些向量元素的地址是一种不可移植的黑客攻击,如果向量存储实际上不是连续的,它将破坏。使用 begin() + 100000 等。
  • 我的错,显然标准保证向量存储是连续的。然而,使用这样的地址是不好的做法,因为它肯定不能保证适用于所有支持随机访问的容器,而 begin() + 100000 是。
  • @j_random_hacker:很抱歉不同意。 std::vector 的 STL 规范已显式更改以支持此类过程。指针也是迭代器的有效类型。查找 iterator_traits
  • @taktak004 不。请记住,operator[] 返回一个引用。只有在您读取或写入引用时,它才会成为访问冲突。由于我们什么都不做,而是得到了我们没有调用 UB 的地址。
【解决方案3】:

这个讨论很老了,但最简单的还没有提到,list-initialization

 vector<int> subvector = {big_vector.begin() + 3, big_vector.end() - 2}; 

需要c++11或以上。

示例用法:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(){

    vector<int> big_vector = {5,12,4,6,7,8,9,9,31,1,1,5,76,78,8};
    vector<int> subvector = {big_vector.begin() + 3, big_vector.end() - 2};

    cout << "Big vector: ";
    for_each(big_vector.begin(), big_vector.end(),[](int number){cout << number << ";";});
    cout << endl << "Subvector: ";
    for_each(subvector.begin(), subvector.end(),[](int number){cout << number << ";";});
    cout << endl;
}

结果:

Big vector: 5;12;4;6;7;8;9;9;31;1;1;5;76;78;8;
Subvector: 6;7;8;9;9;31;1;1;5;76;

【讨论】:

    【解决方案4】:

    std::vector&lt;T&gt;(input_iterator, input_iterator),在您的情况下为foo = std::vector&lt;T&gt;(myVec.begin () + 100000, myVec.begin () + 150000);,例如参见here

    【讨论】:

    • 由于 Andrew 正在尝试构建一个新向量,我建议使用 "std::vector foo(..." 而不是使用 "foo = std::vector(..." 进行复制跨度>
    • 是的,当然,但是无论您键入 std::vector foo = std::vector(...) 还是 std::vector foo (...) 都应该没关系。
    【解决方案5】:

    这些天,我们使用spans!所以你会写:

    #include <gsl/span>
    
    ...
    auto start_pos = 100000;
    auto length = 1000;
    auto span_of_myvec = gsl::make_span(myvec);
    auto my_subspan = span_of_myvec.subspan(start_pos, length);
    

    获得与myvec 相同类型的 1000 个元素的跨度。或者更简洁的形式:

    auto my_subspan = gsl::make_span(myvec).subspan(1000000, 1000);
    

    (但我不太喜欢这样,因为每个数字参数的含义并不完全清楚;如果 length 和 start_pos 处于同一数量级,情况会变得更糟。)

    无论如何,请记住,这不是副本,它只是向量中数据的视图,所以要小心。如果你想要一个实际的副本,你可以这样做:

    std::vector<T> new_vec(my_subspan.cbegin(), my_subspan.cend());
    

    注意事项:

    【讨论】:

    • 将使用 cbegincend 仅作为原则 ;) std::cbegin 等等。
    • @JHBonarius:看到这段代码没有根据容器的选择进行模板化,我看不出有什么特别的好处;我想是口味问题。
    【解决方案6】:

    如果两者都不会被修改(不添加/删除项目 - 只要您注意线程问题,修改现有项目就可以了),您可以简单地绕过 data.begin() + 100000data.begin() + 101000,并假装它们是较小向量的begin()end()

    或者,由于向量存储保证是连续的,您可以简单地传递一个 1000 项数组:

    T *arrayOfT = &data[0] + 100000;
    size_t arrayOfTLength = 1000;
    

    这两种技术都需要恒定的时间,但要求数据的长度不会增加,从而触发重新分配。

    【讨论】:

    • 如果你想让原始向量和子向量链接起来也很好。
    【解决方案7】:

    你没有提到std::vector&lt;...&gt; myVec是什么类型,但是如果它是一个简单的类型或者不包含指针的结构/类,并且你想要最好的效率,那么你可以做一个直接内存复制(我认为会比提供的其他答案更快)。这是std::vector&lt;type&gt; myVec 的一般示例,在这种情况下typeint

    typedef int type; //choose your custom type/struct/class
    int iFirst = 100000; //first index to copy
    int iLast = 101000; //last index + 1
    int iLen = iLast - iFirst;
    std::vector<type> newVec;
    newVec.resize(iLen); //pre-allocate the space needed to write the data directly
    memcpy(&newVec[0], &myVec[iFirst], iLen*sizeof(type)); //write directly to destination buffer from source buffer
    

    【讨论】:

    • 我想知道如果使用 -O3,@Anteru 的“使用构造函数”std::vector(myVec.begin () + 100000, myVec.begin () + 150000);,这个更长的版本不会生成完全相同的程序集吗?
    • MSVC++ 2015,例如,在适当的情况下将std::vector&lt;&gt;(iter, iter) 编译为memmove()(如果构造函数是平凡的,则为平凡的适当定义)。
    • 不要打电话给memcpy。执行std::copy 或接受范围(两个迭代器)的构造函数,编译器和std.library 将在适当时合谋调用memcpy
    【解决方案8】:

    你可以使用insert

    vector<type> myVec { n_elements };
    
    vector<type> newVec;
    
    newVec.insert(newVec.begin(), myVec.begin() + X, myVec.begin() + Y);
    

    【讨论】:

      【解决方案9】:

      当 M 是子向量的大小时,您可以使用具有 O(M) 性能的 STL copy

      【讨论】:

      • 赞成,因为它为我指明了正确的方向,但我明白为什么@LokiAstari 建议它不是正确的选择 - 因为 STL::copy 与两个 std::vector 数组一起使用相同的尺寸和类型。在这里,OP 想要将一个小节复制到一个新的、更小的数组中,如 OP 的帖子中所述:“0
      • @Andrew,查看使用 std::copy 和 std::back_inserter 的示例
      • @LokiAstari 为什么不呢?
      • @LokiAstari 我指的是一个没有通过同行评审的编辑,它提出了示例
        vector newvec; std::copy(myvec.begin()+10000, myvec.begin() +10100, std::back_inserter(newvec));
        在这种情况下,您不需要先构建目标,但可以肯定的是,直接初始化更...直接。
      • @chrisg:它也是两行。此外,您需要插入第三条线路以确保其有效。 newvec.reserve(10100 - 10000);。它绝对是一种选择,从技术上讲它会起作用。但在这两个中,你会推荐哪一个?
      【解决方案10】:

      投影非线性时间集合的唯一方法是懒惰地这样做,其中生成的“向量”实际上是委托给原始集合的子类型。例如,Scala 的List#subseq 方法在恒定时间内创建一个子序列。但是,这仅适用于集合是不可变的并且基础语言支持垃圾收集的情况。

      【讨论】:

      • 在 c++ 中这样做的方法是将 shared_ptr 的向量而不是 X 的向量复制到 X,然后复制 SP,但不幸的是,我认为这不会更快,因为原子操作涉及 cpying SP。或者原始向量可以是向量的 const shared_ptr,而您只需引用其中的子范围。 ofc 你不需要将它设为矢量的 shared_ptr 但是你会遇到终身问题......这一切都在我的脑海中,可能是错误的......
      【解决方案11】:

      假设有两个向量。

       vector<int> vect1{1, 2, 3, 4};
       vector<int> vect2;
      

      方法一、使用复制功能。 copy(first_iterator_index, last_iterator_index, back_inserter()) :- 这个函数有 3 个参数,首先,旧向量的第一个迭代器。其次,旧向量的最后一个迭代器,第三个是 back_inserter 函数,用于从后面插入值。

          // Copying vector by copy function
          copy(vect1.begin(), vect1.end(), back_inserter(vect2));
      

      方法 2. 使用赋值函数。分配(first_iterator_o,last_iterator_o)。此方法为新向量分配与旧向量相同的值。这需要 2 个参数,第一个迭代器到旧向量,最后一个迭代器到旧向量。

          //Copying vector by assign function
          vect2.assign(vect1.begin(), vect1.end());
      

      【讨论】:

        【解决方案12】:

        也许 GSL 库中的 array_view/span 是一个不错的选择。

        这里也是单文件实现:array_view

        【讨论】:

        • 请在此处添加答案以及链接。由于外部链接将来可能会发生变化
        【解决方案13】:

        轻松将元素从一个向量复制到另一个向量
        在这个例子中,我使用成对的向量来使其易于理解
        `

        vector<pair<int, int> > v(n);
        
        //we want half of elements in vector a and another half in vector b
        vector<pair<lli, lli> > a(v.begin(),v.begin()+n/2);
        vector<pair<lli, lli> > b(v.begin()+n/2, v.end());
        
        
        //if v = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
        //then a = [(1, 2), (2, 3)]
        //and b = [(3, 4), (4, 5), (5, 6)]
        
        //if v = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7)]
        //then a = [(1, 2), (2, 3), (3, 4)]
        //and b = [(4, 5), (5, 6), (6, 7)]
        

        '
        如您所见,您可以轻松地将元素从一个向量复制到另一个向量,例如,如果您想将元素从索引 10 复制到 16,那么我们将使用

        vector<pair<int, int> > a(v.begin()+10, v.begin+16);
        

        如果你想要从索引 10 到末尾的某个索引的元素,那么在这种情况下

        vector<pair<int, int> > a(v.begin()+10, v.end()-5);
        

        希望这会有所帮助,请记住最后一种情况v.end()-5 &gt; v.begin()+10

        【讨论】:

          【解决方案14】:

          还有一个选择: 例如在thrust::device_vectorthrust::host_vector 之间移动时很有用,在这种情况下您不能使用构造函数。

          std::vector<T> newVector;
          newVector.reserve(1000);
          std::copy_n(&vec[100000], 1000, std::back_inserter(newVector));
          

          也应该是复杂度O(N)

          您可以将其与顶级代码结合起来

          vector<T>::const_iterator first = myVec.begin() + 100000;
          vector<T>::const_iterator last = myVec.begin() + 101000;
          std::copy(first, last, std::back_inserter(newVector));
          

          【讨论】:

            【解决方案15】:

            只是为了其他人发布这么晚..我敢打赌第一个编码器现在已经完成了。 对于简单的数据类型,不需要复制,只需恢复到良好的旧 C 代码方法即可。

            std::vector <int>   myVec;
            int *p;
            // Add some data here and set start, then
            p=myVec.data()+start;
            

            然后将指针 p 和 len 传递给任何需要子向量的东西。

            notelen 一定是!! len &lt; myVec.size()-start

            【讨论】:

            • 这不会执行复制。
            猜你喜欢
            • 2015-11-01
            • 2022-09-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-08-11
            • 2017-04-01
            • 2011-09-08
            • 1970-01-01
            相关资源
            最近更新 更多