与std :: map等效的remove_if


118

我试图根据特定条件从地图中删除一系列元素。我该如何使用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;
}

您的意思是remove_if不起作用?
2009年

我无法使用remove_if在地图中找到元素,对吧?它给出了编译时错误。我想念什么吗?
aJ。

否-不能像remove_if那样工作,因为remove_if通过对序列进行重新排序,将不符合条件的元素移到最后。因此,它确实适用于T [n],但不适用于map <T,U>。
MSalters

2
使用C + 11,您可以for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}用来减少混乱。休息就像其他人所说的。这个问题为我节省了一些时间;-)
Atul Kumar

Answers:


111

几乎。

for(; iter != endIter; ) {
     if (Some Condition) {
          iter = aMap.erase(iter);
     } else {
          ++iter;
     }
}

如果您确实从迭代器中删除了一个元素,则原来将其增加两次。您可能会跳过需要删除的元素。

这是我在许多地方见过使用和记录的常见算法。

[编辑]您是正确的,迭代器在擦除后会失效,但是仅迭代器引用已擦除的元素,其他迭代器仍然有效。因此iter++erase()通话中使用。


4
我很困惑; 为什么要使用for(; ...;)代替while(...)?此外,尽管这可能可行,但是.erase不会返回下一个迭代器吗?因此,如果(某些条件)博客应该是iter = aMap.erase(iter)才是最兼容的。也许我缺少什么?我缺乏一些人的经验。
taxilian

86
请注意,在C ++ 11中,所有关联容器(包括)map从中返回下一个迭代器erase(iter)。这样做更清洁iter = erase( iter )
Potatoswatter 2012年

10
@taxilian(晚几年)while()或for()可以工作,但是从语义上讲,人们经常使用for()在已知范围内进行迭代,而while()进行未知数量的循环。因为在这种情况下范围是已知的(从开始到endIter),所以for()不会是一个不寻常的选择,并且可能会更常见。但是同样,两者都是可以接受的。
Jamin Gray

4
@taxilian更重要的是:使用'for',您可以将迭代器定义置于循环范围内,因此不会与程序的其余部分混淆。
桑契斯2014年

1
@athos该问题用被动语态表达,“建议”。没有普遍的建议。我认为我的最后评论是最直接的方法。它确实涉及迭代器变量的两个副本,正如有人在此处指出的那样,它失去了一点效率。适合您的是您的电话。
Potatoswatter

75

用于std :: map(和其他容器)的delete_if

为此,我使用以下模板。

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
});

3
@CodeAngry谢谢-在我看来,这似乎一直很奇怪std。我了解为什么它不是的成员std::map,但是我认为类似的东西应该在标准库中。
Iron Savior 2013年

3
将在C ++ 20 forstd::map和其他版本中添加。
罗伊·丹顿


3

我从出色的SGI STL参考中获得了此文档:

Map具有重要的属性,即在地图中插入新元素不会使指向现有元素的迭代器无效。从映射表中删除元素也不会使任何迭代器无效,当然,对于实际指向要删除的元素的迭代器除外。

因此,您要指向要删除的元素的迭代器当然将无效。做这样的事情:

if (some condition)
{
  iterator here=iter++;
  aMap.erase(here)
}

3
这与原始代码没有什么不同。iter ++递增迭代器,然后返回指向递增之前的元素的迭代器。
史蒂夫·弗利

但是iter不会失效,因为我们然后在此处的位置进行擦除
1800信息

@ 1800INFORMATION:进入函数调用是一个序列点,在erase调用之前评估增量副作用。因此,它们确实是等效的。不过,与原始版本相比,我还是非常希望您的版本。
peterchen

它适用于数组或向量,但会在stl映射中导致意外结果。
hunter_tech

2

原始代码只有一个问题:

for(; iter != endIter; ++iter)
{
    if(Some Condition)
    {
        // is it safe ?
        aMap.erase(iter++);
    }
}

此处iter,在for循环中递增一次,在擦除中递增一次,这很可能以某个无限循环结束。



1

从下面的注释:

http://www.sgi.com/tech/stl/PairAssociativeContainer.html

对关联容器不能提供可变的迭代器(如琐碎的迭代器要求中所定义),因为可变的迭代器的值类型必须是可分配的,而对则不可分配。但是,配对关联容器可以提供并非完全恒定的迭代器:这样的迭代器使得表达式(* i).second = d有效。


1

第一

Map具有重要的属性,即在地图中插入新元素不会使指向现有元素的迭代器无效。从映射表中删除元素也不会使任何迭代器无效,当然,对于实际指向要删除的元素的迭代器除外。

二,以下代码不错

for(; iter != endIter; )
{
    if(Some Condition)
    {
        aMap.erase(iter++);
    }
    else
    {
        ++iter;
    }
}

调用函数时,将在调用该函数之前先评估参数。

因此,当在调用擦除之前评估iter ++时,迭代器的++运算符将返回当前项,并指向调用后的下一项。


1

恕我直言,没有remove_if()等效项。
您无法重新排序地图。
因此remove_if(),不能将您的兴趣放在可以通话的末端erase()


真的很不幸
allyourcode 2014年

1

基于Iron Savior的答案对于那些想提供更多类似于std函数的迭代器的范围的人。

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项并从迭代器中获取它。


1
“标识符以下划线开头,后跟大写字母,以供实现使用。”
YSC

0

史蒂夫·弗利(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
看来效率低下。
allyourcode 2014年


0

我这样使用

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.