从std :: vector

时间:2016-12-12 12:04:48

标签: c++ algorithm stl stdvector std-pair

在我的一个项目中,有必要从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);
    }
}

6 个答案:

答案 0 :(得分:2)

由于您可以假设间隔不重叠并且正在增加顺序,因此解决方案是从后面开始(以便索引不会更改)并依次删除每个范围:

因此,您要求的最小代码量为:

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

这方面的缺点是这将涉及大量的矢量改组。你真正要做的是将矢量末尾的最后一个未换行的项目与要删除的项目交换,然后在完成切断结束时调整大小;但这需要更多代码。

答案 1 :(得分:1)

我以为我发布了一个更具容错能力的答案。如果您的间隔大于输入数组,例如intervals包含{15, 15},则仍然可以正常运行。此外,这比UKMonkey's solution更快,因为它只需一次完成所有工作:

我注意到这段代码是实现定义的,只适用于Clang and 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)); })));

<击> Live Example

你可以在for循环中完成同样的事情:

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);

Live Example

如果你迷上&#34;实现这一目标所需的最短代码量,&#34;你也可以在, - 循环中使用lambda中的邪恶for

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];

答案 2 :(得分:1)

这个问题非常重要,因为在第一次调用vector::erase()之后,所有索引/迭代器到第一个擦除的元素之后的元素都会失效,包括要删除的更多间隔。

因此,使用vector::erase()必须按要删除的元素的降序进行。

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

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

答案 3 :(得分:1)

您确实需要的是一种解决方案,不仅具有短代码,而且效率高,可以最大限度地减少值向量中的副本和移位。

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

float_literal

对于第二部分,最短且最有效的是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将缩小向量。从算法的角度来看,这种解决方案可能是最优的。它应该支付大型载体。

答案 4 :(得分:1)

作为Matt Timmermans的补充:这不是问题,但是如果您只想在区间中保留值,则在C ++ 17中,您可以编写:

String myString = barcode.displayvalue;
if(myString.contains("CZC")){
    myString = "replaced";
}

答案 5 :(得分:0)

嗯,到目前为止的答案都很糟糕 - 无论是制作全新的载体还是需要O(N ^ 2)时间 - 所以我会加上这个。

不是删除你不想保留的元素,而是每次都移动其余元素,而是将想要保持的元素移动到正确的位置,然后截断向量

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());
 }