从 STL 列表中删除项目

Posted

技术标签:

【中文标题】从 STL 列表中删除项目【英文标题】:Erasing items from an STL list 【发布时间】:2009-02-02 01:15:18 【问题描述】:

我想创建一个函数,如果它们符合特定条件,则将项目从一个 STL 列表移动到另一个。

此代码不是执行此操作的方法。迭代器很可能会被 erase() 函数失效并导致问题:

for(std::list<MyClass>::iterator it = myList.begin(); it != myList.end(); it++)

  if(myCondition(*it))
  
    myOtherList.push_back(*it);
    myList.erase(it);
  

那么任何人都可以提出更好的方法吗?

【问题讨论】:

【参考方案1】:

Erasereturns an iterator指向擦除后的元素:

std::list<MyClass>::iterator it = myList.begin();
while (it != myList.end())

  if(myCondition(*it))
  
    myOtherList.push_back(*it);
    it = myList.erase(it);
  
  else
  
    ++it;
  

【讨论】:

我通常使用erase(it++)而不是依赖于erase的返回值,因为其他STL容器并不总是返回一个更新的迭代器。 @haggai_e:标准要求 erase() 方法返回相关的迭代器。不遵循这一点的实现不符合标准,应该避免。 @haggai_e:每个 STL 容器都返回一个更新的迭代器。另一方面,不能保证您的 ++ 技巧适用于所有迭代器。 (例如,在向量中,它将失效,因此您将无法增加它) @wilhemltell,@jalf:不正确。 std::map 和其他关联容器类从擦除函数返回 void。但是,任何序列都从擦除返回一个后续迭代器。【参考方案2】:

STL 列表有一个有趣的特性:splice() 方法可以让您破坏性地将元素从一个列表移动到另一个列表。

splice() 在恒定时间内运行,不会复制元素或执行任何空闲存储分配/释放。请注意,两个列表必须属于同一类型,并且它们必须是单独的列表实例(而不是对同一列表的两个引用)。

这是一个如何使用 splice() 的示例:

for(std::list<MyClass>::iterator it = myList.begin(); it != myList.end(); ) 
    if(myCondition(*it)) 
        std::list<MyClass>::iterator oldIt = it++;
        myOtherList.splice(myOtherList.end(), myList, oldIt);
     else 
        ++it;
    

【讨论】:

【参考方案3】:

解决方案 1

template<typename Fwd, typename Out, typename Operation>
Fwd move_if(Fwd first, Fwd last, Out result, Operation op)

    Fwd swap_pos = first;
    for( ; first != last; ++first ) 
        if( !op(*first) ) *swap_pos++ = *first;
        else *result++ = *first;
    
    return swap_pos;

这个想法很简单。如果谓词为真,您要做的是从一个容器中删除 元素并将它们放置在另一个容器中。因此,采用已经完成删除部分的std::remove() 算法的代码,并根据您的额外需求对其进行调整。在上面的代码中,我添加了else 行以在谓词为真时复制元素。

请注意,因为我们使用std::remove() 代码,算法实际上并没有缩小输入容器。虽然它确实返回了输入容器的更新结束迭代器,所以你可以只使用它而忽略额外的元素。如果您真的想缩小输入容器,请使用erase-remove idiom。

解决方案 2

template<typename Bidi, typename Out, typename Operation>
Bidi move_if(Bidi first, Bidi last, Out result, Operation op)

    Bidi new_end = partition(first, last, not1(op));
    copy(new_end, last, result);
    return new_end;

第二种方法使用 STL 来实现算法。我个人觉得它比第一个解决方案更具可读性,但它有两个缺点:首先,它需要更强大的输入容器双向迭代器,而不是我们在第一个解决方案中使用的前向迭代器。其次,这对您来说可能是也可能不是问题,不能保证容器的顺序与调用std::partition() 之前的顺序相同。如果您希望保持顺序,请将该调用替换为对std::stable_partition() 的调用。 std::stable_partition() 可能会稍微慢一些,但它的运行时复杂度与 std::partition() 相同。

任一方式:调用函数

list<int>::iterator p = move_if(l1.begin(), l1.end(),
                                back_inserter(l2),
                                bind2nd(less<int>(), 3));

结语

在编写代码时我遇到了一个难题:move_if() 算法应该返回什么?一方面,算法应该返回一个指向输入容器新结束位置的迭代器,因此调用者可以使用擦除删除习惯来缩小容器。但另一方面,算法应该返回结果容器末尾的位置,否则调用者找到它可能会很昂贵。在第一个解决方案中,result 迭代器在算法结束时指向该位置,而在第二个解决方案中,std::copy() 返回的迭代器指向该位置。我可以返回一对迭代器,但为了简单起见,我只返回一个迭代器。

【讨论】:

【参考方案4】:
std::list<MyClass>::iterator endMatching =
    partition(myList.begin(), myList.end(), myCondition);
myOtherList.splice(myOtherList.begin(), myList, endMatching, myList.end());

请注意,partition() 足以区分匹配对象和不匹配对象。 (但 list::splice() 很便宜)

请参阅以下有关受启发的具体案例的代码 Now to remove elements that match a predicate?

#include <iostream>
#include <iterator>
#include <list>
#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()

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

        // Linear. Exactly last - first applications of pred, and at most (last - first)/2 swaps. 
        list<string>::iterator end1 =
        partition(Strings.begin(), Strings.end(), Pred);

        list<string> NotMatching;

        // This function is constant time. 
        NotMatching.splice(NotMatching.begin(),Strings, Strings.begin(), end1); 

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

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

        return 0;

【讨论】:

【参考方案5】:

另一个尝试:

for(std::list<MyClass>::iterator it = myList.begin(); it != myList.end; ) 
    std::list<MyClass>::iterator eraseiter = it;
    ++it;
    if(myCondition(*eraseiter)) 
        myOtherList.push_back(*eraseiter);
        myList.erase(eraseiter);
    

【讨论】:

你是说remove_if吗?另外, myList.erase 会引起问题,不是吗?否则,这就是我要发布的内容。 =]【参考方案6】:
template <typename ForwardIterator, typename OutputIterator, typename Predicate>
void splice_if(ForwardIterator begin, ForwardIterator end, OutputIterator out, Predicate pred)

    ForwardIterator it = begin;
    while( it != end )
    
        if( pred(*it) )
        
            *begin++ = *out++ = *it;
        
        ++it;
    
    return begin;


myList.erase( 
    splice_if( myList.begin(), myList.end(), back_inserter(myOutputList),
        myCondition
    ),
    myList.end()
)

【讨论】:

不起作用。首先,如果您决定使用迭代器,那么您将无法缩小列表。其次,您无法访问算法内部的 myList 。第三,out 是一个输出迭代器。 对,我可能会在未来修复代码......现在太晚了,只是删除它。 为什么 splice_if() 不调用 splice() 方法?

以上是关于从 STL 列表中删除项目的主要内容,如果未能解决你的问题,请参考以下文章

创建和管理STL列表的子列表

如何从列表中删除项目并返回新列表而不在颤动中删除项目

使用 STL 从字符串中删除重复字符

从过滤器列表和原始列表中删除项目

在迭代时从地图(或任何其他STL容器)中删除/删除内容

从 Flutter 的列表中删除项目