【问题标题】:Inserting multiple values into a vector at specific positions在特定位置将多个值插入向量中
【发布时间】:2019-12-02 11:46:58
【问题描述】:

假设我有一个这样的整数向量 std::vector<int> _data; 我知道如果我想从_data 中删除多个项目,那么我可以简单地调用

_data.erase( std::remove_if( _data.begin(), _data.end(), [condition] ), _data.end() );

这比eraseing 多个元素要快得多,因为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_ 版本,但在某些地方,顺序确实很重要。

【问题讨论】:

  • 也许reserve()resie() 会有所帮助?
  • vector的内部容量已经足够了(预分配20万左右)
  • 如果您在中间插入了很多内容,那么您不应该使用std::vector,而是使用其他东西。什么是“别的东西”?它取决于实际应用 - 您的代码解决的问题。所以基本上你的问题来自XY problem
  • 据我所知,没有标准的库算法。您可能必须自己编写。
  • @walnut,对不起,只是一个例子。我已经换了顺序。

标签: c++ vector


【解决方案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 

【讨论】:

  • 我喜欢你的回答。我要么必须做一些修改以使用迭代器而不是位置,要么我可以包装它,并使用distance。我发现了一种稍微好一点的方法,它不使用迭代器或位置,而是使用插入排序。编辑:我实际上只是将它与vector.insert 进行了比较,两者都比vector 慢。 :(
【解决方案2】:

好的,我们需要一些假设。让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 算法的问题是谓词作用于值而不是迭代器,这对这个问题有点烦人。

【讨论】:

    【解决方案3】:

    这是我的看法:

    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

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

    请注意,我们在此处用于确定插入项目位置的索引都是插入前。我发现这是一个潜在的混淆点(这是一个限制 - 你不能在这个算法的最后添加一个元素。也可以通过在第二个循环期间调用 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 处插入的含义(您最终会在两者之间找到一个原始元素吗?在第一个 sn-p 中你会,在第二个你不会' t)。你可以多次插入同一个索引吗?插入前的索引是有意义的,而插入后的索引则没有。你也可以通过将当前的*it(即键值对)传递给函子来写这个,但这似乎不太有用......

    【讨论】:

      【解决方案4】:

      这是我做的一个尝试,它以相反的顺序插入。我确实为此摆脱了迭代器/索引。

      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,vector 的 insert 更快(插入 5 个数字)。在我的机器 MSVC 上,结果相同但不那么严重。我还从他的回答中与马克西姆的版本进行了比较。我意识到使用 Godbolt 并不是一个很好的比较方法,但我无法访问我当前机器上的其他 3 个编译器。

      https://godbolt.org/z/vjV2wA

      我机器上的结果:

      我的插入:659us
      马克西姆插入:712us
      矢量插入:315us

      神螺栓的 ICC

      我的插入:470us
      马克西姆插入:139us
      矢量插入:127us

      Godbolt 的 GCC

      我的插入:815us
      马克西姆插入:97us
      矢量插入:97us

      神箭的叮当声:

      我的插入:477us
      马克西姆插入:188us
      矢量插入:96us

      【讨论】:

        猜你喜欢
        • 2021-07-24
        • 1970-01-01
        • 1970-01-01
        • 2013-10-29
        • 1970-01-01
        • 2013-07-10
        • 1970-01-01
        • 2017-09-10
        • 1970-01-01
        相关资源
        最近更新 更多