题
我试图根据特定条件从地图中删除一系列元素。我如何使用STL算法?
最初我想过使用remove_if
但是不可能因为remove_if不适用于关联容器。
是否有<!>“remove_if <!>”;适用于地图的等效算法?
作为一个简单的选项,我想到了遍历地图并擦除。但是循环遍历地图并擦除安全选项?(因为迭代器在擦除后变得无效)
我使用了以下示例:
bool predicate(const std::pair<int,std::string>& x)
{
return x.first > 2;
}
int main(void)
{
std::map<int, std::string> aMap;
aMap[2] = "two";
aMap[3] = "three";
aMap[4] = "four";
aMap[5] = "five";
aMap[6] = "six";
// does not work, an error
// std::remove_if(aMap.begin(), aMap.end(), predicate);
std::map<int, std::string>::iterator iter = aMap.begin();
std::map<int, std::string>::iterator endIter = aMap.end();
for(; iter != endIter; ++iter)
{
if(Some Condition)
{
// is it safe ?
aMap.erase(iter++);
}
}
return 0;
}
解决方案
几乎。
for(; iter != endIter; ) {
if (Some Condition) {
aMap.erase(iter++);
} else {
++iter;
}
}
如果你从中删除了一个元素,那么你最初会增加迭代器两次;你可能会跳过需要删除的元素。
这是我见过的常用算法,并在很多地方都有记录。
[编辑]你是正确的,擦除后迭代器无效,但只有迭代器引用被擦除的元素,其他迭代器仍然有效。因此在erase()调用中使用iter ++。
其他提示
erase_if for std :: map(和其他容器)
我使用以下模板。
namespace stuff {
template< typename ContainerT, typename PredicateT >
void erase_if( ContainerT& items, const PredicateT& predicate ) {
for( auto it = items.begin(); it != items.end(); ) {
if( predicate(*it) ) it = items.erase(it);
else ++it;
}
}
}
这不会返回任何内容,但会删除std :: map中的项目。
用法示例:
// 'container' could be a std::map
// 'item_type' is what you might store in your container
using stuff::erase_if;
erase_if(container, []( item_type& item ) {
return /* insert appropriate test */;
});
第二个例子(允许你传入一个测试值):
// 'test_value' is value that you might inject into your predicate.
// 'property' is just used to provide a stand-in test
using stuff::erase_if;
int test_value = 4; // or use whatever appropriate type and value
erase_if(container, [&test_value]( item_type& item ) {
return item.property < test_value; // or whatever appropriate test
});
我从优秀的SGI STL参考资料获得此文档:
地图具有重要的属性 将新元素插入地图 不会使迭代器失效 指向现有元素。擦除 地图中的元素也没有 使任何迭代器无效,除了 当然,对于实际的迭代器 指向正在存在的元素 擦除。
因此,指向要删除的元素的迭代器当然会失效。做这样的事情:
if (some condition)
{
iterator here=iter++;
aMap.erase(here)
}
原始代码只有一个问题:
for(; iter != endIter; ++iter)
{
if(Some Condition)
{
// is it safe ?
aMap.erase(iter++);
}
}
这里iter
在for循环中增加一次,在erase中再增加一次,这可能会在某个无限循环中结束。
现在,std::experimental::erase_if
在标题<experimental/map>
中可用。
从底部注释:
http://www.sgi.com/tech/stl/PairAssociativeContainer.html
Pair关联容器不能提供可变迭代器(如Trivial Iterator要求中所定义),因为可变迭代器的值类型必须是Assignable,而pair不是Assignable。但是,Pair关联容器可以提供不完全恒定的迭代器:迭代器使得表达式(* i).second = d有效。
第一
Map具有以下重要特性:将新元素插入到映射中不会使指向现有元素的迭代器无效。从地图中删除元素也不会使任何迭代器无效,当然,除了实际指向正在被删除的元素的迭代器之外。
其次,以下代码是好的
for(; iter != endIter; )
{
if(Some Condition)
{
aMap.erase(iter++);
}
else
{
++iter;
}
}
调用函数时,将在调用该函数之前评估参数。
因此,在调用erase之前评估iter ++时,迭代器的++运算符将返回当前项,并指向调用后的下一项。
恕我直言,没有remove_if()
等效。
你不能重新排序地图。
所以erase()
不能把你的兴趣对放在你可以调用<=>的末尾。
基于 Iron Savior的回答对于那些希望提供更多范围的标准功能的人来说
template< typename ContainerT, class _FwdIt, class _Pr >
void erase_if(ContainerT& items, _FwdIt it, _FwdIt _Last, _Pr _Pred) {
for (; it != _Last; ) {
if (_Pred(*it)) it = items.erase(it);
else ++it;
}
}
好奇是否有某种方法可以丢失ContainerT项并从迭代器中获取它。
Steve Folly的回答我觉得效率更高。
这是另一个简单但效率低下的解决方案:
解决方案使用remove_copy_if
将我们想要的值复制到新容器中,然后将原始容器的内容与新容器的内容交换:
std::map<int, std::string> aMap;
...
//Temporary map to hold the unremoved elements
std::map<int, std::string> aTempMap;
//copy unremoved values from aMap to aTempMap
std::remove_copy_if(aMap.begin(), aMap.end(),
inserter(aTempMap, aTempMap.end()),
predicate);
//Swap the contents of aMap and aTempMap
aMap.swap(aTempMap);
如果要删除键大于2的所有元素,那么最好的方法是
map.erase(map.upper_bound(2), map.end());
仅适用于范围,不适用于任何谓词。
我像这样使用
std::map<int, std::string> users;
for(auto it = users.begin(); it <= users.end()) {
if(<condition>){
it = users.erase(it);
} else {
++it;
}
}