从向量中提取子向量的最佳方法?


295

假设我有一个std::vectormyVec大小)N。构造一个由元素X到Y的副本组成的新矢量的最简单方法是什么,其中0 <= X <= Y <= N-1?例如,myVec [100000]通过myVec [100999]一个size的向量150000

如果使用向量无法有效完成此操作,是否应该使用另一种STL数据类型呢?


7
您说要提取一个子向量,但在我看来,您真正想要的是视图/对子向量的访问-区别在于视图无法复制-老式的C ++将使用开始指针和结束指针,考虑到std :: vector上的mem是连续的,那么应该可以使用指针进行迭代,从而避免复制,但是如果您不介意复制,则只需使用先前的范围初始化一个新的向量矢量
serup

自c ++ 11起就有.data()(cplusplus.com/reference/vector/vector/data)。然而,使用指针的STL容器内气馁,看到stackoverflow.com/questions/31663770/...
大卫·托特

Answers:


371
vector<T>::const_iterator first = myVec.begin() + 100000;
vector<T>::const_iterator last = myVec.begin() + 101000;
vector<T> newVec(first, last);

构造新向量是O(N)操作,但实际上没有更好的方法。


12
+1,也就是O(YX),它小于或等于O(N)(在他的示例中更小)
orip

74
@orip好吧,那毕竟是O(N)。
约翰·杰雷尔

55
@GregRogers:使用大O表示法(其中N是特定数字)没有意义。Big-O传达了有关N变化的增长率。约翰:最好不要以两种方式使用一个变量名。我们通常会说O(Y-X),或者我们会说O(Z) where Z=Y-X
Mooing Duck 2013年

2
@GregRogers通过这种方式,我们需要声明一个新的向量。有没有办法改变原始向量?像myVec(first,last)之类的东西?我知道这是错误的,但是我确实需要解决方案,因为我想在代码中使用递归,并且需要重复使用相同的向量(尽管已更改)。谢谢!
ulyssis2 2015年

13
为什么不只是vector<T> newVec(myVec.begin() + 100000, myVec.begin() + 101000);呢?
aquirdturtle

88

只需使用向量构造函数即可。

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

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

2
好的,我没有意识到从任意向量元素中获得迭代器是如此简单。
2009年

5
获取这些向量元素的地址是一个不可移植的技巧,如果向量存储实际上不是连续的,它将无法使用。使用begin()+ 100000,等等
。– j_random_hacker

2
糟糕的是,很显然,该标准保证了向量存储是连续的。尽管如此,使用这样的地址是一种不好的做法,因为肯定不能保证对所有支持随机访问的容器都起作用,而begin()+ 100000则可以。
j_random_hacker

33
@j_random_hacker:对不起,我不同意。std :: vector的STL规范已明确更改为支持这种类型的过程。指针也是迭代器的有效类型。查找iterator_traits <>
马丁·约克

6
@ taktak004不。请记住,它operator[]返回一个参考。只有在您读取或写入参考文献的时候,它才会成为访问冲突。由于我们既不执行任何操作,而是获取地址,因此我们尚未调用UB ,。
马丁·约克

28

std::vector<T>(input_iterator, input_iterator),以您的情况foo = std::vector<T>(myVec.begin () + 100000, myVec.begin () + 150000);为例,请参见此处


1
由于安德鲁正在尝试构建新的向量,因此我建议使用“ std :: vector foo(...”,而不是使用“ foo = std :: vector(...”)进行复制
Drew Dormann

4
是的,当然,但是无论您键入std :: vector <int> foo = std :: vector(...)还是std :: vector <int> foo(...)都无关紧要。
09年

19

这些天,我们使用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);

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

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

1
@JHBonarius:看到此代码不是如何根据容器的选择而模板化的,所以我看不出有什么特别的好处。我想我的口味
einpoklum

10

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

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

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

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


如果要链接原始向量和子向量,这也很好。
PyRulez'3

7

这个讨论已经很老了,但是最简单的讨论还没有提到,它是通过列表初始化的

 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;

6

您没有提到什么类型std::vector<...> myVec,但是如果它是不包含指针的简单类型或结构/类,并且想要获得最佳效率,那么您可以执行直接内存复制(我认为它将比提供其他答案)。下面是一个普通的例子std::vector<type> myVec,其中type在这种情况下int

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

2
我想知道@Anteru的“使用构造函数”是否使用-O3,std::vector(myVec.begin () + 100000, myVec.begin () + 150000);将这种农产品的更长版本转换为完全相同的程序集吗?
桑索恩18'Feb

1
例如,MSVC ++ 2015会编译std::vector<>(iter, iter)memmove(),如果合适的话(如果构造函数是琐碎的,那么对于琐碎的适当定义)。
Pablo H

1
不要打电话memcpy。做一个std::copy或一个接受范围的构造函数(两个迭代器),编译器和std.library将合谋memcpy在适当的时候调用。
-Bulletmagnet

4

你可以用 insert

vector<type> myVec { n_elements };

vector<type> newVec;

newVec.insert(newVec.begin(), myVec.begin() + X, myVec.begin() + Y);

3

当M是子向量的大小时,可以将STL复制与O(M)性能一起使用。


由于它向我指出了正确的方向,所以提出了建议,但是我可以理解为什么@LokiAstari建议这不是正确的选择-因为STL :: copy可与两个大小和类型相同的std :: vector <T>数组一起使用。在这里,OP希望将子节复制到一个新的,较小的数组中,如OP的文章中所述:“ 0 <= X <= Y <= N-1”
Andrew

@Andrew,请参阅使用std :: copy和std :: back_inserter的示例
chrisg

@LokiAstari为什么不呢?
chrisg

2
@LokiAstari我指的是对它的编辑,该编辑在同行评审中无法幸免,因此提出了<br/> vector <T> newvec示例;std :: copy(myvec.begin()+ 10000,myvec.begin()+10100,std :: back_inserter(newvec)); <br/>在这种情况下,您不需要先构建目标,但是可以肯定,直接初始化更...直接。
chrisg

1
@chrisg:它也是两行。另外,您需要插入第三行以确保其有效。newvec.reserve(10100 - 10000);。IT绝对是一种选择,从技术上讲,它将起作用。但是,您要推荐哪两个呢?
马丁·约克

1

投影非线性时间集合的唯一方法是懒惰地进行投影,其中生成的“向量”实际上是委托给原始集合的子类型。例如,Scala的List#subseq方法会在恒定时间内创建一个子序列。但是,这仅在该集合是不可变的并且基础语言使用了垃圾回收时才有效。


以c ++的方式做到这一点将是将shared_ptr的向量转换为X而不是X的向量,然后复制SP,但是不幸的是,我不认为这会更快,因为原子操作涉及cpying SP。或者,原始向量可以改为vector的const shared_ptr,而您只是引用其中的子范围。ofc,您不需要使其成为vector的shared_ptr,但是您一生都遇到了问题...所有这些都超出了我的头,可能是错误的...
NoSenseEtAl 2013年

0

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

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

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

notelen一定是! len < myVec.size()-start


这不会执行复制。
Trilarion


0

复制从一个向量到另一个元素容易
在这个例子中,我使用对的载体,可以很容易地理解
`

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 > v.begin()+10


0

还有另一种选择:例如在a thrust::device_vector和a 之间移动时很有用thrust::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));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.