现在删除与谓词匹配的元素?

时间:2009-03-02 21:13:33

标签: c++ stl

我有一个字符串的源容器我想从源容器中删除与谓词匹配的任何字符串,并将它们添加到目标容器中。

remove_copy_if和其他算法只能重新排序容器中的元素,因此必须由erase成员函数跟进。我的书(Josuttis)说remove_copy_if在目标容器中的最后一个位置之后返回一个迭代器。因此,如果我只在目标容器中有一个迭代器,我如何在源容器上调用erase?我已经尝试使用目标的大小来确定从源容器的末尾回去多远,但没有运气。我只提出了以下代码,但它会进行两次调用(remove_ifremove_copy_if)。

有人能让我知道正确的方法吗?我确定两个线性调用不是 这样做的方法。

#include <iostream>
#include <iterator>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>

using namespace std;  

class CPred : public unary_function<string, bool>
{
public:
        CPred(const string& arString)
                :mString(arString)
        {
        }

        bool operator()(const string& arString) const
        {
                return (arString.find(mString) == std::string::npos);
        }
private:
        string mString;
};

int main()
{
        vector<string> Strings;
        vector<string> Container;

        Strings.push_back("123");
        Strings.push_back("145");
        Strings.push_back("ABC");
        Strings.push_back("167");
        Strings.push_back("DEF");

        cout << "Original list" << endl;
        copy(Strings.begin(), Strings.end(),ostream_iterator<string>(cout,"\n"));

        CPred Pred("1");

        remove_copy_if(Strings.begin(), Strings.end(),
                       back_inserter(Container),
                       Pred);

        Strings.erase(remove_if(Strings.begin(), Strings.end(),
                      not1(Pred)), Strings.end());

        cout << "Elements beginning with 1 removed" << endl;
        copy(Strings.begin(), Strings.end(),ostream_iterator<string>(cout,"\n"));

        cout << "Elements beginning with 1" << endl;
        copy(Container.begin(), Container.end(),ostream_iterator<string>(cout,"\n"));

        return 0;
}

6 个答案:

答案 0 :(得分:7)

尽管弗雷德的辛勤工作得到应有的尊重,但我要补充一点:move_ifremove_copy_if在抽象层面上没有什么区别。唯一的实现级别更改是end()迭代器。你还没有得到任何erase()接受的答案不是erase()匹配的元素 - OP问题陈述的一部分。

关于OP的问题:你想要的是就地拼接。这可用于列表。但是,使用vectors这将无效。了解迭代器何时以及如何以及为何失效。你必须采用两遍算法。

  

remove_copy_if和其他算法只能重新排序容器中的元素,

来自SGI关于remove_copy_if的文档:

  

此操作是稳定的,这意味着复制的元素的相对顺序与[first,last]范围内的相对顺序相同。

因此不会发生相对重新排序。此外,这是一个副本,这意味着您案例中Source vector的元素正在复制到Container vector

  

如何在源容器上调用erase?

您需要使用另一种名为remove_if的算法:

  

remove_if从每个元素[first, last)中移除x范围,以使pred(x)成立。也就是说,remove_if返回迭代器new_last,使得范围[first, new_last)不包含pred为真的元素。范围[new_last, last)中的迭代器仍然可以解除引用,但它们指向的元素未指定。 Remove_if是稳定的,这意味着未删除的元素的相对顺序不变。

因此,只需将remove_copy_if调用更改为:

vector<string>::iterator new_last = remove_if(Strings.begin(), 
                                              Strings.end(), 
                                              Pred);

你已经准备好了。请记住,Strings vector的范围不再是迭代器[first(), end())定义的范围,而是[first(), new_last)

如果您愿意,可以通过以下方式删除剩余的[new_last, end())

Strings.erase(new_last, Strings.end());

现在,您的vector已缩短,end()new_last相同(超过最后一个元素),因此您可以一如既往地使用:

copy(Strings.begin(), Strings.end(), ostream_iterator(cout, "\"));

在控制台上打印字符串(stdout)。

答案 1 :(得分:5)

我明白你的观点,你要避免对源容器进行两次传递。不幸的是,我不相信有一个标准算法可以做到这一点。可以创建自己的算法,将元素复制到新容器并从源容器中删除(与remove_if相同;之后必须进行擦除)。您的容器大小和性能要求将决定创建此类算法的努力是否优于两次通过。

编辑:我提出了一个快速实施:

template<typename F_ITER, typename O_ITER, typename FTOR>
F_ITER move_if(F_ITER begin, F_ITER end, O_ITER dest, FTOR match)
{
  F_ITER result = begin;
  for(; begin != end; ++begin)
  {
    if (match(*begin))
    {
      *dest++ = *begin;
    }
    else
    {
      *result++ = *begin;
    }
  }

  return result;
}

编辑: 也许“通行证”意味着混淆。在OP的解决方案中,调用remove_copy_if()并调用remove_if()。每个都将遍历整个原始容器。然后调用erase()。这将遍历从原始容器中删除的任何元素。

如果我的算法用于将删除的元素复制到新容器(使用begin(),输出迭代器的原始容器将无法工作,如Dirkgently演示),它将执行一次传递,将删除的元素复制到通过back_inserter或某种此类机制的新容器。与remove_if()一样,仍然需要擦除。消除了对原始容器的一次通过,我认为这是OP所追求的。

答案 2 :(得分:2)

将有copy_if和remove_if。

copy_if( Strings.begin(), Strings.end(), 
         back_inserter(Container), not1(Pred) );
Strings.erase( remove_if( Strings.begin(), Strings.end(), not1(Pred) ), 
               Strings.end() );

如果存在某些内容,最好理解Predicate类回答“true”的代码。在这种情况下,你不需要两次。

因为std :: find从开头查找子字符串不是必须的,所以你需要将“从1开始”更改为“with 1”,以避免将来误解你的代码。

答案 3 :(得分:1)

  1. remove_ *算法不擦除元素的全部原因是因为单独使用迭代器不可能“擦除”元素。 您无法通过迭代器获取容器 这一点在“有效STL”

  2. 一书中有更详细的解释
  3. 使用“copy_if”,然后使用“remove_if”。 remove_copy_if不会修改来源。

  4. 在列表上你可以做得更好 - 重新排序然后拼接。

答案 4 :(得分:1)

如果您不介意将字符串放在同一容器中,并且只使用迭代器来分隔它们,则此代码可以正常工作。

#include "stdafx.h"

#include <iostream>
#include <iterator>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>

using namespace std;

class CPred : public unary_function<string, bool>
{
public:
        CPred(const string& arString)
                :mString(arString)
        {
        }

        bool operator()(const string& arString) const
        {
                return (arString.find(mString) == std::string::npos);
        }
private:
        string mString;
};

int main()
{
        vector<string> Strings;

        Strings.push_back("213");
        Strings.push_back("145");
        Strings.push_back("ABC");
        Strings.push_back("167");
        Strings.push_back("DEF");

        cout << "Original list" << endl;
        copy(Strings.begin(), Strings.end(),ostream_iterator<string>(cout,"\n"));

        CPred Pred("1");

        vector<string>::iterator end1 = 
        partition(Strings.begin(), Strings.end(), Pred);

        cout << "Elements matching with 1" << endl;
        copy(end1, Strings.end(), ostream_iterator<string>(cout,"\n"));

        cout << "Elements not matching with 1" << endl;
        copy(Strings.begin(), end1, ostream_iterator<string>(cout,"\n"));

        return 0;
}

答案 5 :(得分:0)

remove *()不会重新删除元素,它只是重新排序它们并将它们放在集合的末尾,并在同一容器中返回一个new_end迭代器,指示新结束的位置。然后,您需要调用erase来从矢量中删除范围。

source.erase(source.remove(source.begin(), source.end(), element), source.end());

remove_if()使用谓词执行相同的操作。

source.erase(source.remove_if(source.begin(), source.end(), predicate), source.end());

remove_copy_if()只会复制与谓词不匹配的元素,保持源向量不变,并为目标向量提供结束迭代器,以便缩小它。

// target must be of a size ready to accomodate the copy
target.erase(source.remove_copy_if(source.begin(), source.end(), target.begin(), predicate), target.end());