说我有一个像这样的整数std::vector<int> _data;
我知道,如果我想从_data
中删除多个项目,则只需致电
_data.erase( std::remove_if( _data.begin(), _data.end(), [condition] ), _data.end() );
这比erase
处理多个元素快得多,因为vector
中需要较少的数据移动。我想知道是否有类似的插入内容。
例如,如果我有以下几对
auto pair1 = { _data.begin() + 5, 5 };
auto pair2 = { _data.begin() + 12, 12 };
我可以使用现有的std
函数在一次迭代中插入这两个函数吗?我知道我可以做类似的事情:
_data.insert( pair2.first, pair2.second );
_data.insert( pair1.first, pair1.second );
但是对于大向量(谈论100,000多个元素),这(非常)缓慢。
编辑:基本上,我有一个自定义集(和映射),它使用vector
作为基础容器。我知道我可以只使用std::set
或std::map
,但是我进行遍历的次数远远超过了插入/删除的次数。从set
和map
切换到此自定义设置/地图已减少了20%的运行时间。不过,目前,插入操作约占剩余运行时间的10%,因此减少插入时间很重要。
很遗憾,还需要订购。我尽可能使用unordered_
版本,但是在某些地方顺序很重要。
答案 0 :(得分:1)
一种方法是创建另一个向量,该向量的容量等于原始大小加上要插入的元素的数量,然后执行没有重新分配的插入循环,复杂度为O(N):
template<class T>
std::vector<T> insert_elements(std::vector<T> const& v, std::initializer_list<std::pair<std::size_t, T>> new_elements) {
std::vector<T> u;
u.reserve(v.size() + new_elements.size());
auto src = v.begin();
size_t copied = 0;
for(auto const& element : new_elements) {
auto to_copy = element.first - copied;
auto src_end = src + to_copy;
u.insert(u.end(), src, src_end);
src = src_end;
copied += to_copy;
u.push_back(element.second);
}
u.insert(u.end(), src, v.end());
return u;
}
int main() {
std::vector<int> v{1, 3, 5};
for(auto e : insert_elements(v, {{1,2}, {2,4}}))
std::cout << e << ' ';
std::cout << '\n';
}
输出:
1 2 3 4 5
答案 1 :(得分:0)
好的,我们需要一些假设。令old_end
为向量的最后一个元素的反向迭代器。假设您的_data
的大小已调整为完全适合其当前内容和您要插入的内容。假设inp
是std::pair
的容器,其中包含要反向插入的要插入数据(因此,首先插入要在最后面的位置的元素,依此类推)。然后我们可以做:
std::merge(old_end, _data.rend(), inp.begin(), inp.end(), data.rend(), [int i = inp.size()-1](const &T t, const &std::pair<Iter, T> p) mutable {
if( std::distance(_data.begin(), p.first) == i ) {
--i;
return false;
}
return true;
}
但是我认为这并不比使用旧的for
更清楚。 stl算法的问题在于谓词对值起作用,而不对迭代器起作用,这使这个问题有点烦人。
答案 2 :(得分:0)
这是我的看法:
template<class Key, class Value>
class LinearSet
{
public:
using Node = std::pair<Key, Value>;
template<class F>
void insert_at_multiple(F&& f)
{
std::queue<Node> queue;
std::size_t index = 0;
for (auto it = _kvps.begin(); it != _kvps.end(); ++it)
{
// The container size is left untouched here, no iterator invalidation.
if (std::optional<Node> toInsert = f(index))
{
queue.push(*it);
*it = std::move(*toInsert);
}
else
{
++index;
// Replace current node with queued one.
if (!queue.empty())
{
queue.push(std::move(*it));
*it = std::move(queue.front());
queue.pop();
}
}
}
// We now have as many displaced items in the queue as were inserted,
// add them to the end.
while (!queue.empty())
{
_kvps.emplace_back(std::move(queue.front()));
queue.pop();
}
}
private:
std::vector<Node> _kvps;
};
这是一个线性时间算法,不需要先验知道插入元素的数量。对于每个索引,它要求在其中插入一个元素。如果得到一个,则将相应的现有矢量元素推送到队列中,并用新的替换。否则,它将当前项目提取到队列的后面,并将该项目放在队列的前面,放入当前位置(如果尚未插入任何元素,则为空)。请注意,在所有这些过程中,矢量大小保持不变。仅在最后,我们才将仍在队列中的所有项目回退。
请注意,我们在这里用于确定插入项目位置的索引都是插入前。我发现这可能会引起混淆(这是一个局限性-您无法使用此算法在最后添加一个元素。也可以通过在第二个循环中调用f
来解决此问题,从而加以补救。 ..)。
这是一个允许在末尾(以及其他所有位置)任意插入许多元素的版本。它会将插入后索引传递给函子!
template<class F>
void insert_at_multiple(F&& f)
{
std::queue<Node> queue;
std::size_t index = 0;
for (auto it = _kvps.begin(); it != _kvps.end(); ++it)
{
if (std::optional<Node> toInsert = f(index))
queue.push(std::move(*toInsert));
if (!queue.empty())
{
queue.push(std::move(*it));
*it = std::move(queue.front());
queue.pop();
}
++index;
}
// We now have as many displaced items in the queue as were inserted,
// add them to the end.
while (!queue.empty())
{
if (std::optional<Node> toInsert = f(index))
{
queue.push(std::move(*toInsert));
}
_kvps.emplace_back(std::move(queue.front()));
queue.pop();
++index;
}
}
同样,这会给在索引0和1处插入的含义造成混淆(您是否最终在两个之间插入原始元素?在第一个代码段中,您会在第二个代码段中却没有) 。您可以多次插入同一索引吗?插入前索引有意义,而插入后索引则没有意义。您也可以通过将当前的*it
(即键值对)传递给函子来编写此代码,但是仅此一点似乎并不太有用...
答案 3 :(得分:0)
这是我所做的尝试,它以相反的顺序插入。我确实摆脱了这个迭代器/索引。
template<class T>
void insert( std::vector<T> &vector, const std::vector<T> &values ) {
size_t last_index = vector.size() - 1;
vector.resize( vector.size() + values.size() ); // relies on T being default constructable
size_t move_position = vector.size() - 1;
size_t last_value_index = values.size() - 1;
size_t values_size = values.size();
bool isLastIndex = false;
while ( !isLastIndex && values_size ) {
if ( values[last_value_index] > vector[last_index] ) {
vector[move_position] = std::move( values[last_value_index--] );
--values_size;
} else {
isLastIndex = last_index == 0;
vector[move_position] = std::move( vector[last_index--] );
}
--move_position;
}
if ( isLastIndex && values_size ) {
while ( values_size ) {
vector[move_position--] = std::move( values[last_value_index--] );
--values_size;
}
}
}
在Godbolt上尝试使用ICC,Clang和GCC,矢量的insert
更快(插入5个数字)。在我的机器上,MSVC,结果相同,但不太严重。我还将他的回答与Maxim的版本进行了比较。我意识到使用Godbolt并不是比较的好方法,但是我无法使用当前计算机上的其他3个编译器。
我的机器上的结果:
我的插入内容:659us
Maxim插入:712us
矢量插入:315us
Godbolt的ICC
我的插入内容:470us
Maxim插入:139us
矢量插入:127us
Godbolt的GCC
我的插入内容:815us
Maxim插入:97us
矢量插入:97us
哥德的叮当声
我的插入内容:477us
Maxim插入:188us
矢量插入:96us