在特定位置将多个值插入向量

时间:2019-12-02 11:46:58

标签: c++ vector

说我有一个像这样的整数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::setstd::map,但是我进行遍历的次数远远超过了插入/删除的次数。从setmap切换到此自定义设置/地图已减少了20%的运行时间。不过,目前,插入操作约占剩余运行时间的10%,因此减少插入时间很重要。

很遗憾,还需要订购。我尽可能使用unordered_版本,但是在某些地方顺序很重要。

4 个答案:

答案 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的大小已调整为完全适合其当前内容和您要插入的内容。假设inpstd::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;
};

https://godbolt.org/z/EStKgQ

这是一个线性时间算法,不需要先验知道插入元素的数量。对于每个索引,它要求在其中插入一个元素。如果得到一个,则将相应的现有矢量元素推送到队列中,并用新的替换。否则,它将当前项目提取到队列的后面,并将该项目放在队列的前面,放入当前位置(如果尚未插入任何元素,则为空)。请注意,在所有这些过程中,矢量大小保持不变。仅在最后,我们才将仍在队列中的所有项目回退。

请注意,我们在这里用于确定插入项目位置的索引都是插入前。我发现这可能会引起混淆(这是一个局限性-您无法使用此算法在最后添加一个元素。也可以通过在第二个循环中调用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;
    }
}

https://godbolt.org/z/DMuCtJ

同样,这会给在索引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个编译器。

https://godbolt.org/z/vjV2wA

我的机器上的结果:

  

我的插入内容:659us
  Maxim插入:712us
  矢量插入:315us

Godbolt的ICC

  

我的插入内容:470us
  Maxim插入:139us
  矢量插入:127us

Godbolt的GCC

  

我的插入内容:815us
  Maxim插入:97us
  矢量插入:97us

哥德的叮当声

  

我的插入内容:477us
  Maxim插入:188us
  矢量插入:96us