擦除范围从std :: vector删除向量的最佳方法

Best way to erase vector of ranges from std::vector

本文关键字:向量 最佳 方法 删除 vector 范围 std 擦除      更新时间:2023-10-16

在我的一个项目中,有必要从std::vector<double> values中删除某些元素。我必须删除的索引作为间隔向量给出。例如,{1,3}的意思是,我必须从values中删除1至3个索引。

我可以假设给出的间隔是相互排斥的。

下面显示的代码说明了所需的行为应该是什么样的。

#include <iostream>
#include <vector>
int main(int argc, char** args) {
    // Intervals of indices I have to remove from values
    std::vector<std::pair<int, int>> intervals = { {1,3},{7,9},{13,13} }; 
    // Vector of arbitrary values. 
    std::vector<double> values = {4.2,6.4,2.3,3.4,9.1,2.3,0.6,1.2,0.3,0.4,6.4,3.6,1.4,2.5,7.5 }
    removeIntervals(values, intervals);
    // intervals should contain 4.2,9.1,2.3,0.6,6.4,3.6,1.4,7.5
}

实现此目标所需的最短代码可能是多少?

到目前为止,我最好的解决方案是:

 void removeIntervals(std::vector<double>& values, const std::vector < std::pair<int, int>>& intervals) {
    std::vector<bool> flags(values.size(), true);
    std::vector<double> ret;
    for (auto interval : intervals) {
        std:fill(flags.begin() + interval.first, flags.begin()+interval.second+1, false);
    }
    for (auto i = 0; i < values.size(); i++) {
        if (flags[i]) ret.push_back(values[i]);
    }
    values = ret;
 }

我可以假设,我的间隔是不重叠的和连续的。看来,它归结为从前到前的擦除。

void removeIntervals2(std::vector<double>& values, const std::vector < std::pair<int, int>>& intervals) {
    auto revIntervals = intervals;
    std::reverse(revIntervals.begin(), revIntervals.end());
    for (auto interval : revIntervals) {
        values.erase(std::begin(values) + interval.first, std::begin(values) + interval.second + 1);
    }
}

,因为您可以假定间隔不会重叠并增加顺序,所以解决方案是在背面启动(以使索引不会更改)并删除每个范围转:

因此,对于最少的代码,您要求的代码:

for (auto& it = intervals.rbegin(); it != intervals.rend(); ++it) {
  values.erase(values.begin() + it->first, std::next(values.begin() + it->second));

下面的一面是,这将涉及大量的矢量改组。的确,您想做的是在矢量末尾交换最后一个未换的物品,并使用要删除的项目,然后在完成时调整大小以切断末端;但这需要更多代码。

问题是非平凡的,因为在第一次呼叫vector::erase()所有索引/迭代器之后,第一个删除的元素被无效,包括进一步的间隔要删除。

因此,必须按要删除元素的降序顺序完成使用vector::erase()

另一种不便源于使用int索引而不是迭代器在间隔边界上。最后,vector::erase()副本(矿石移动)所有元素都超过了最后删除的元素以填补空白。这保留了值的顺序,但在几个间隔的情况下会导致过度复制(移动)。

一种更有效的方法是仅交换要删除的元素,最后缩小矢量大小。

您肯定想要的解决方案不仅具有短代码,而且是良好的效率,可以最大程度地减少值的副本和变化。

我肯定会选择解决方案的第一部分,那就是要保留或删除的位置。

std::vector<bool> flags(values.size(), true);
for (auto interval : intervals) {
    std:fill(flags.begin() + interval.first, flags.begin()+interval.second+1, false);
}

在第二部分中,最短,最有效的是erase/remove_if成语:

 values.erase(std::remove_if(begin(values), end(values),
    [&](const auto& v) { return !flags[&v - &(*values.begin())];}),
  values.end());

此处的效率是由于remove_if将首先 Mark 需要删除的元素,然后它将通过将第一个元素保留并返回第一个元素的位置来压缩矢量去除。最后,erase将收缩矢量。从算法的角度来看,该解决方案可能是最佳的。它应该为大量向量支付。

以为我会发布一个更容易容忍的答案。如果您的间隔大于输入阵列,例如,如果包括intervals包含{15, 15},则此仍然可以正常运行。此外,这比Ukmonkey的解决方案更快,因为它可以在单个通行证中完成所有工作:

我已经注意到该代码已定义,并且仅在Clang和Visual Studio 2015 Update 3:

上工作。
values.resize(distance(begin(values), remove_if(begin(values), end(values), [i = 0U, it = cbegin(intervals), end = cend(intervals)](const auto&) mutable { return it != end && ++i > it->first && (i <= it->second || (++it, true)); })));

实时示例

您可以在for -Loop中完成相同的事情:

size_t write = 0U;
auto it = cbegin(intervals);
for (size_t read = 0U; read < size(values); ++read) {
    if (it == cend(intervals) || read < it->first) {
        values[write++] = values[read];
    } else if (read == it->second) {
        ++it;
    }
}
values.resize(write);

实时示例

如果您挂在"实现此目标所需的最短代码"上,则可以在for -Loop中使用我的邪恶,

for (size_t read = 0U; read < size(values); ++read) if (it == cend(intervals) || read < it->first || (read == it->second && (++it, false))) values[write++] = values[read];

好吧,到目前为止的答案都是不好的 - 要么制造全新的向量,要么需要O(n^2)时间 - 所以我会添加这个。

,而不是删除您不想保留的元素,并每次移动其余的元素,而是将其移动 do 想要保持适当的位置,然后只是将矢量截断。

o(n)时间,没有额外的空间:

void removeIntervals(std::vector<double>& values, const std::vector < std::pair<int, int>>& intervals) {
    if (intervals.size()<=0)
        return;
    //keep the part before the first interval
    auto dest = values.begin()+intervals[0].first;
    for (size_t i=0; i<intervals.size(); ++i) {
        //copy the part to keep after each interval
        auto s = values.cbegin()+intervals[i].second+1;
        auto e = (i+i >= intervals.size() ?
                  values.cend() : 
                  values.cbegin()+intervals[i+1].first);
        while(s<e) {
            *dest++=*s++;
        }
    }
    values.erase(dest,values.end());
 }

在补充马特·蒂默曼(Matt Timmermans)答案:这不是问题,但是如果您只想在c 17中仅保留值,则可以写下:

void remove_if_not_in_interval(std::vector<double>& result, const std::vector<std::pair<int,int> >& intervals)
    {
      if (intervals.size() == 0)
        result.clear();
      auto dest = result.begin();
      for (auto [first, last] : intervals)
        {
          while(first!=last+1)
            {
              *dest++ = *(result.begin() + first++);
            }
        }
      result.erase(dest,result.end());
    }