我正在使用多线程并希望合并结果。例如:
std::vector<int> A;
std::vector<int> B;
std::vector<int> AB;
我希望将A和B的内容按顺序合并到AB中,如何最有效地实现?
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
并预留前置,我不会感到惊讶。 - GManNickGinsert
的性能保证。 - Nick Bastinsize < capacity
大部分时间都成立,分支预测很可能会导致非重新分配分支的指令在指令流水线中,最小化分支引起的延迟,除了低迭代计数。这假设有一个良好的向量实现,加上 CPU 指令流水线和[良好的]分支预测,但这些对于现代工具链和台式机来说是相当可靠的假设。不过我不知道智能手机怎么样。 - boycy这取决于你是否真的需要物理连接这两个向量,或者你只是想为了迭代而给出连接的外观。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不会将这两个向量复制到新容器中,而是生成一对迭代器(范围),涵盖两个容器的跨度。可能会有一些性能开销,但也许比首先将所有数据复制到新容器中要少。
append_range
方法。
AB.append_range(A);
AB.append_range(B);
就是这样了!
时间复杂度为O(|A| + |B|)。对于那些只需要“看起来像连接”的情况,下面描述了一个O(1)(常数时间)的替代方法。
std::vector<int> A{ 1, 2, 3, 4, 5};
std::vector<int> B{ 10, 20, 30 };
vproxy<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 vproxy {
private:
std::vector<T>& v1, v2;
public:
vproxy(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& vproxy<T>::operator[](const size_t& i) const{
return (i < v1.size()) ? v1[i] : v2[i - v1.size()];
};
template <class T>
const size_t vproxy<T>::size() const { return v1.size() + v2.size(); };
主要优势
创建它的时间复杂度是O(1)(常数时间),并且只需要最少的额外内存分配。
一些需要考虑的事项
基于Kiril V. Lyadvinsky的答案,我创建了一个新版本。这个代码片段使用模板和重载。使用它,你可以写出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
}
::
已经被占用了;) - aloisdgoperator+=
**),最好不要在insert
之前调用reserve
。请参阅关于reserve
的这些说明。但对于另一个运算符(operator+
)来说,这是有意义的。 - user5534993insert()
比reserve()
更可取。因此,你应该不使用reserve()
。然而,该注释将其与具有push_back()
的特定示例进行了比较,而您没有使用它们。我的第一个想法是说可以省略reserve()
。但是,我不会拿自己的生命去赌。值得提出一个单独的问题。 - user5534993还有一种未被提到的简单变体:
copy(A.begin(),A.end(),std::back_inserter(AB));
copy(B.begin(),B.end(),std::back_inserter(AB));
对于这种情况,如果您事先知道每个线程产生的结果数量,则可以预先分配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);
…
我的答案基于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();
}
};
template <class T1, class T2>
void ContainerInsert(T1 t1, T2 t2)
{
t1->insert(t1->end(), t2->begin(), t2->end());
}
这样你就可以避免像这样的临时放置:
ContainerInsert(vec, GetSomeVector());
<algorithm>
的set_union。set_union(A.begin(), A.end(), B.begin(), B.end(), AB.begin());
在链接中有一个更全面的示例。