从 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】:Erase
returns 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 列表中删除项目的主要内容,如果未能解决你的问题,请参考以下文章