使用std :: remove_if跟踪已删除的元素

时间:2012-03-05 08:18:27

标签: c++ stl

我想从vector中移除一些元素并使用remove_if算法来执行此操作。但我想跟踪已删除的元素,以便稍后我可以对它们执行某些操作。我用以下代码尝试了这个:

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;


struct IsEven
{
    bool operator()(int n) 
    {
        if(n % 2 == 0)
        {
            evens.push_back(n);
            return true;
        }

        return false;
    }

    vector<int> evens;
};

int main(int argc, char **argv)
{

    vector<int> v;
    for(int i = 0; i < 10; ++i)
    {
        v.push_back(i);
    }

    IsEven f;
    vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), f);
    for(vector<int>::iterator it = f.evens.begin(); it != f.evens.end(); ++it)
    {
        cout<<*it<<"\n";
    }

    v.erase(newEnd, v.end());

    return 0;
}

但是这不起作用,因为remove_if接受我的仿函数对象的副本,因此无法访问存储的evens向量。实现这一目标的正确方法是什么?

P.S。 :例如,偶数和赔率仅仅是为了清酒,我的真实代码是不同的。因此,不建议以不同方式识别偶数或赔率。

6 个答案:

答案 0 :(得分:9)

解决方案不是remove_if,而是堂兄 partial_sort partition。区别在于remove_if仅保证[begin, middle)包含匹配元素,但partition也保证[middle, end)包含与谓词不匹配的元素。

因此,您的示例变得公正(请注意,不再需要evens):

vector<int>::iterator newEnd = partition(v.begin(), v.end(), f);
for(vector<int>::iterator it = newEnd; it != v.end(); ++it)
{
    cout<<*it<<"\n";
}
v.erase(newEnd, v.end());

答案 1 :(得分:3)

你最好的选择是std::partition(),它将重新排列序列中的所有elts,例如你的谓词返回true将位于其返回false的所有elts之前。

例:

vector<int>::iterator bound = partition (v.begin(), v.end(), IsEven);
std::cout << "Even numbers:" << std::endl;
for (vector<int>::iterator it = v.begin(); it != bound; ++it)
  std::cout << *it << " ";

std::cout << "Odd numbers:" << std::endl;
for (vector<int>::iterator it = bound; it != v.end(); ++it)
  std::cout << *it << " ";

答案 2 :(得分:2)

如果您通过参考传递ist,则可以避免复制您的仿函数(即按值传递):

vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), 
   boost::bind<int>(boost::ref(f), _1));

如果您无法使用boost,则std::ref也可以使用{{1}}。我测试了上面的代码,它按预期工作。

答案 3 :(得分:1)

额外的间接级别。在本地声明向量,和 让IsEven包含一份副本。 IsEven也可以 拥有该向量,前提是它是由动态分配和管理的 一个shared_ptr。在实践中,我一般都找到了局部变量 加指针解决方案更方便。类似的东西:

class IsEven
{
    std::vector<int>* myEliminated;
public:
    IsEven( std::vector<int>* eliminated = NULL )
        : myEliminated( eliminated )
    {
    }
    bool
    operator()( int n ) const
    {
        bool results = n % 2 == 0;
        if ( results && myEliminated != NULL ) {
            myEliminated->push_back( n );
        }
        return results;
    }
}

请注意,这也允许operator()()函数为const。一世 认为这是正式要求的(虽然我不确定)。

答案 4 :(得分:0)

我在代码中看到的问题是,每次remove_if算法调用时,都会在struct中创建evens向量。所以不管你是否将一个仿函数传递给remove_if它每次都会创建一个新的向量。因此,一旦删除了最后一个元素,当函数调用结束并从函数中退出时,f.evens将始终获取一个空向量。这可以用两种方式分类,

  1. 用类替换struct并将evens声明为static(如果这是你想要的)
  2. 或者你可以让evens全球化。我不会亲自推荐(它会使代码变坏,对全局变量说不,除非你真的需要它们)。
  3. 编辑:

    正如nabulke所建议的,你也可以std :: ref喜欢这个,std :: ref(f)。这可以防止您将矢量设置为全局并避免不必要的静态。

    使其全球化的样本如下,

    #include <vector>
    #include <algorithm>
    #include <iostream>
    
    using namespace std;
    vector<int> evens;
    
    struct IsEven
    {
        bool operator()(int n) 
        {
            if(n % 2 == 0)
            {
                evens.push_back(n);
                return true;
            }
    
            return false;
        }
    
    
    };
    
    int main(int argc, char **argv)
    {
    
        vector<int> v;
        for(int i = 0; i < 10; ++i)
        {
            v.push_back(i);
        }
    
        IsEven f;
        vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), f);
        for(vector<int>::iterator it = evens.begin(); it != evens.end(); ++it)
        {
            cout<<*it<<"\n";
        }
    
        v.erase(newEnd, v.end());
    
        return 0;
    }
    

    这段代码对我来说似乎很合适。如果这不是您想要的,请告诉我。

答案 5 :(得分:0)

您可能有其他解决方案;只有你不需要在同一时间删除elts(是吗?)。使用std::for_each()返回您的仿函数的副本。例:

IsEven result = std::for_each(v.begin(), v.end(), IsEven());

// Display the even numbers.
std::copy(result.evens.begin(), result.evens.end(), std::ostream_iterator<int> (cout, "\n"));  

请注意,尽可能在c ++中创建未命名的变量总是更好。在这里,该解决方案并不能完全解决您的主要问题(从源容器中删除elts),但它会提醒所有人std :: for_each()返回您的仿函数的副本。 : - )