我正在使用多线程并希望合并结果。例如:
std::vector<int> A;
std::vector<int> B;
std::vector<int> AB;
我希望 AB 必须按顺序获得 A 的内容和 B 的内容。做这样的事情最有效的方法是什么?
AB.reserve( A.size() + B.size() ); // preallocate memory
AB.insert( AB.end(), A.begin(), A.end() );
AB.insert( AB.end(), B.begin(), B.end() );
这正是成员函数 std::vector::insert
的用途
std::vector<int> AB = A;
AB.insert(AB.end(), B.begin(), B.end());
insert
并预先保留,我不会感到惊讶。
distance
具有 O(1) 复杂度)。尽管如此,当您通常可以通过提前计划做得更好时,需要注意 insert
的性能保证。
size < capacity
在大多数情况下,分支预测可能会导致非重新分配分支的指令位于指令流水线中,从而最大限度地减少分支引起的延迟,但迭代次数较少。这假设一个良好的向量实现,加上 CPU 指令流水线 & [good] 分支预测,但对于现代工具链和台式机来说,这些都是非常可靠的假设。虽然不知道智能手机..
取决于您是否真的需要在物理上连接两个向量,或者为了迭代而想要给出连接的外观。 boost::join 函数
http://www.boost.org/doc/libs/1_43_0/libs/range/doc/html/range/reference/utilities/join.html
会给你这个。
std::vector<int> v0;
v0.push_back(1);
v0.push_back(2);
v0.push_back(3);
std::vector<int> v1;
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
...
BOOST_FOREACH(const int & i, boost::join(v0, v1)){
cout << i << endl;
}
应该给你
1
2
3
4
5
6
注意 boost::join 不会将两个向量复制到新容器中,而是生成一对覆盖两个容器跨度的迭代器(范围)。会有一些性能开销,但可能比首先将所有数据复制到新容器要少。
在 Bradgonesurfing 的答案的方向上,很多时候人们并不真正需要连接两个向量 (O(n)),而只是像连接它们一样使用它们 (O(1))。如果这是您的情况,则无需 Boost 库即可完成。
诀窍是创建一个向量代理:一个包装类,它操作对两个向量的引用,在外部被视为单个连续的。
用法
std::vector<int> A{ 1, 2, 3, 4, 5};
std::vector<int> B{ 10, 20, 30 };
VecProxy<int> AB(A, B); // ----> O(1). No copies performed.
for (size_t i = 0; i < AB.size(); ++i)
std::cout << AB[i] << " "; // 1 2 3 4 5 10 20 30
执行
template <class T>
class VecProxy {
private:
std::vector<T>& v1, v2;
public:
VecProxy(std::vector<T>& ref1, std::vector<T>& ref2) : v1(ref1), v2(ref2) {}
const T& operator[](const size_t& i) const;
const size_t size() const;
};
template <class T>
const T& VecProxy<T>::operator[](const size_t& i) const{
return (i < v1.size()) ? v1[i] : v2[i - v1.size()];
};
template <class T>
const size_t VecProxy<T>::size() const { return v1.size() + v2.size(); };
主要好处
创建它是 O(1)(恒定时间),并且额外的内存分配最少。
需要考虑的一些事情
只有当你真的知道在处理参考文献时你在做什么时,你才应该这样做。该解决方案旨在针对所提出问题的特定目的,并且效果很好。如果您不确定引用的工作方式,在任何其他上下文中使用它可能会导致意外行为。
在此示例中,AB 不提供非常量访问运算符 ([ ])。随意包含它,但请记住:由于 AB 包含引用,因此为其分配值也会影响 A 和/或 B 中的原始元素。无论这是否是一个理想的功能,这是一个特定于应用程序的问题,应该仔细考虑。
直接对 A 或 B 进行的任何更改(如赋值、排序等)也将“修改”AB。这不一定是坏事(实际上,它可能非常方便:AB 永远不需要显式更新以保持自身与 A 和 B 同步),但这肯定是一种必须注意的行为。重要的例外:将 A 和/或 B 的大小调整到更大可能会导致它们在内存中重新分配(为了需要连续空间),这反过来会使 AB 无效。
因为每次访问一个元素之前都会有一个测试(即“i < v1.size()”),VecProxy 访问时间虽然恒定,但也比向量慢一点。
这种方法可以推广到 n 个向量。我没试过,但应该没什么大不了的。
基于Kiril V. Lyadvinsky answer,我制作了一个新版本。这个片段使用模板和重载。有了它,您可以编写 vector3 = vector1 + vector2
和 vector4 += vector3
。希望它可以提供帮助。
template <typename T>
std::vector<T> operator+(const std::vector<T> &A, const std::vector<T> &B)
{
std::vector<T> AB;
AB.reserve(A.size() + B.size()); // preallocate memory
AB.insert(AB.end(), A.begin(), A.end()); // add A;
AB.insert(AB.end(), B.begin(), B.end()); // add B;
return AB;
}
template <typename T>
std::vector<T> &operator+=(std::vector<T> &A, const std::vector<T> &B)
{
A.reserve(A.size() + B.size()); // preallocate memory without erase original data
A.insert(A.end(), B.begin(), B.end()); // add B;
return A; // here A could be named AB
}
::
被采取了;)
v1 + v2
不代表加法。
@
另一种尚未提及的简单变体:
copy(A.begin(),A.end(),std::back_inserter(AB));
copy(B.begin(),B.end(),std::back_inserter(AB));
并使用合并算法:
#include <algorithm>
#include <vector>
#include <iterator>
#include <iostream>
#include <sstream>
#include <string>
template<template<typename, typename...> class Container, class T>
std::string toString(const Container<T>& v)
{
std::stringstream ss;
std::copy(v.begin(), v.end(), std::ostream_iterator<T>(ss, ""));
return ss.str();
};
int main()
{
std::vector<int> A(10);
std::vector<int> B(5); //zero filled
std::vector<int> AB(15);
std::for_each(A.begin(), A.end(),
[](int& f)->void
{
f = rand() % 100;
});
std::cout << "before merge: " << toString(A) << "\n";
std::cout << "before merge: " << toString(B) << "\n";
merge(B.begin(),B.end(), begin(A), end(A), AB.begin(), [](int&,int&)->bool {});
std::cout << "after merge: " << toString(AB) << "\n";
return 1;
}
所有解决方案都是正确的,但我发现编写一个函数来实现它更容易。像这样:
template <class T1, class T2>
void ContainerInsert(T1 t1, T2 t2)
{
t1->insert(t1->end(), t2->begin(), t2->end());
}
这样你就可以避免像这样的临时放置:
ContainerInsert(vec, GetSomeVector());
对于这个用例,如果您事先知道每个线程产生的结果数,您可以预先分配 AB
并将 std::span
传递给每个线程。这样就不需要进行串联。例子:
std::vector<int> AB(total_number_of_results, 0);
std::size_t chunk_length = …;
std::size_t chunk2_start = chunk_length;
std::size_t chunk3_start = 2 * chunk_length; // If needed
…
// Pass these to the worker threads.
std::span<int> A(AB.data(), chunk_length);
std::span<int> B(AB.data() + chunk2_start, chunk_length);
…
我的回答是基于 Mr.Ronald Souza 的原始解决方案。除了他原来的解决方案,我还写了一个支持迭代器的向量代理!
对不了解原始解决方案上下文的人的简短描述:joined_vector 模板类(即向量代理)将两个向量的两个引用作为构造函数参数,然后将它们视为一个连续向量。我的实现还支持前向迭代器。
用法:
int main()
{
std::vector<int> a1;
std::vector<int> a2;
joined_vector<std::vector<int>> jv(a1,a2);
for (int i = 0; i < 5; i++)
a1.push_back(i);
for (int i = 5; i <=10; i++)
a2.push_back(i);
for (auto e : jv)
std::cout << e<<"\n";
for (int i = 0; i < jv.size(); i++)
std::cout << jv[i] << "\n";
return 0;
}
执行:
template<typename _vec>
class joined_vector
{
_vec& m_vec1;
_vec& m_vec2;
public:
struct Iterator
{
typedef typename _vec::iterator::value_type type_value;
typedef typename _vec::iterator::value_type* pointer;
typedef typename _vec::iterator::value_type& reference;
typedef std::forward_iterator_tag iterator_category;
typedef std::ptrdiff_t difference_type;
_vec* m_vec1;
_vec* m_vec2;
Iterator(pointer ptr) :m_ptr(ptr)
{
}
Iterator operator++()
{
if (m_vec1->size() > 0 && m_ptr == &(*m_vec1)[m_vec1->size() - 1] && m_vec2->size() != 0)
m_ptr = &(*m_vec2)[0];
else
++m_ptr;
return m_ptr;
}
Iterator operator++(int)
{
pointer curr = m_ptr;
if (m_vec1->size() > 0 && m_ptr == &(*m_vec1)[m_vec1->size() - 1] && m_vec2->size() != 0)
m_ptr = &(*m_vec2)[0];
else
++m_ptr;
return curr;
}
reference operator *()
{
return *m_ptr;
}
pointer operator ->()
{
return m_ptr;
}
friend bool operator == (Iterator& itr1, Iterator& itr2)
{
return itr1.m_ptr == itr2.m_ptr;
}
friend bool operator != (Iterator& itr1, Iterator& itr2)
{
return itr1.m_ptr != itr2.m_ptr;
}
private:
pointer m_ptr;
};
joined_vector(_vec& vec1, _vec& vec2) :m_vec1(vec1), m_vec2(vec2)
{
}
Iterator begin()
{
//checkes if m_vec1 is empty and gets the first elemet's address,
//if it's empty then it get's the first address of the second vector m_vec2
//if both of them are empty then nullptr is returned as the first pointer
Iterator itr_beg((m_vec1.size() != 0) ? &m_vec1[0] : ((m_vec2.size() != 0) ? &m_vec2[0] : nullptr));
itr_beg.m_vec1 = &m_vec1;
itr_beg.m_vec2 = &m_vec2;
return itr_beg;
}
Iterator end()
{
//check if m_vec2 is empty and get the last address of that vector
//if the second vector is empty then the m_vec1's vector/the first vector's last element's address is taken
//if both of them are empty then a null pointer is returned as the end pointer
typename _vec::value_type* p = ((m_vec2.size() != 0) ? &m_vec2[m_vec2.size() - 1] : ((m_vec1.size()) != 0 ? &m_vec1[m_vec1.size() - 1] : nullptr));
Iterator itr_beg(p != nullptr ? p + 1 : nullptr);
itr_beg.m_vec1 = &m_vec1;
itr_beg.m_vec2 = &m_vec2;
return itr_beg;
}
typename _vec::value_type& operator [](int i)
{
if (i < m_vec1.size())
return m_vec1[i];
else
return m_vec2[i - m_vec1.size()];
}
size_t size()
{
return m_vec1.size() + m_vec2.size();
}
};
如果您的向量已排序*,请查看 <algorithm>
中的 set_union。
set_union(A.begin(), A.end(), B.begin(), B.end(), AB.begin());
链接中有一个更详尽的示例。
template<typename T>
vector<T> CombineVectors(vector<T> &&a, vector<T> &&b) {
vector<T> ab = move(a);
ab.insert(
ab.end(), make_move_iterator(b.begin()), make_move_iterator(b.end()));
return ab;
}
template<typename T>
vector<T> CombineVectors(const vector<T> &a, const vector<T> &b) {
vector<T> ab = a;
ab.insert(ab.end(), b.begin(), b.end());
return ab;
}
不定期副业成功案例分享