【问题标题】:remove_if equivalent for std::map等效于 std::map 的 remove_if
【发布时间】:2010-10-22 12:07:58
【问题描述】:

我试图根据特定条件从地图中删除一系列元素。如何使用 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 不起作用是什么意思?
  • 我不能使用 remove_if 在地图中查找元素,对吧?它给出了编译时错误。我错过了什么吗?
  • 不-它不起作用,因为 remove_if 通过重新排序序列来工作,将不符合条件的元素移动到最后。因此它确实适用于 T[n],但不适用于 map.
  • 使用 C+11,您可以使用for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....} 来减少混乱。休息就像其他人说的那样。这个问题让我刚才省了一些头发分裂;-)
  • 我看到 C++20 有 std::erase_if 和 std::map ...如果我可以将我的代码传输到未来。

标签: c++ stl map


【解决方案1】:

几乎。

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

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

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

[编辑] 您是正确的,迭代器在擦除后无效,但只有引用被擦除元素的迭代器,其他迭代器仍然有效。因此在erase() 调用中使用iter++。

【讨论】:

  • 我很困惑;为什么要使用 for(; ... ;) 而不是 while(...)?此外,虽然这可能有效,但 .erase 不返回下一个迭代器吗?所以看起来 if (Some Condition) 博客应该是 iter = aMap.erase(iter) 是最兼容的。也许我错过了什么?我缺乏你们中一些人的经验。
  • 注意,在 C++11 中,所有关联容器,包括 map,都从 erase(iter) 返回下一个迭代器。 iter = erase( iter ) 做起来更干净。
  • @taxilian (晚年) while() 或 for() 可以工作,但在语义上,人们经常使用 for() 来迭代已知范围,而 while() 用于未知数量的循环.由于在这种情况下范围是已知的(从开始到 endIter),因此 for() 不会是一个不寻常的选择,并且可能会更常见。但同样,两者都是可以接受的。
  • @taxilian 更重要的是:使用 'for',您可以在循环范围内定义迭代器,因此它不会与程序的其余部分混淆。
  • @athos 这个问题用被动语态表达,“推荐”。没有普遍的建议。我认为我的最后一条评论是最直接的方式。它确实涉及迭代器变量的两个副本,正如有人在此处指出的那样,这会降低一些效率。适合您的一切由您决定。
【解决方案2】:

erase_if 用于 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
});

【讨论】:

  • @CodeAngry 谢谢——我总是觉得这在std 中不存在很奇怪。我理解为什么它不是std::map 的成员,但我认为它应该在标准库中。
  • 将在C++20 for std::map等中添加。
【解决方案3】:

现在,std::experimental::erase_if 在标题 &lt;experimental/map&gt; 中可用。

见:http://en.cppreference.com/w/cpp/experimental/map/erase_if

【讨论】:

  • 现在是 C++20
【解决方案4】:

我从excellent SGI STL reference 获得了这份文档:

地图有一个重要的属性: 将新元素插入地图 不会使迭代器无效 指向现有元素。擦除一个 地图中的元素也没有 使任何迭代器无效,除了 当然,对于实际上 指向正在执行的元素 删除。

因此,指向要擦除的元素的迭代器当然会失效。做这样的事情:

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

【讨论】:

  • 这与原始代码没有什么不同。 iter++ 递增迭代器,然后返回一个指向递增前元素的迭代器。
  • 但是iter不会失效,因为我们会在here的位置进行擦除
  • @1800INFORMATION:进入函数调用是一个序列点,在调用erase 之前评估增量副作用。所以它们确实是等价的。尽管如此,我还是更喜欢你的版本而不是原版。
  • 它适用于数组或向量,但会导致stl map中的意外结果。
【解决方案5】:

这里有一些优雅的解决方案。

for (auto it = map.begin(); it != map.end();)
{   
    (SomeCondition) ? map.erase(it++) : (++it);
}

【讨论】:

    【解决方案6】:

    原代码只有一个问题:

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

    这里iter在for循环中增加一次,在erase中增加一次,这可能会以无限循环结束。

    【讨论】:

      【解决方案7】:

      来自以下注释:

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

      Pair Associative Container 不能提供可变迭代器(如 Trivial Iterator 要求中所定义),因为可变迭代器的值类型必须是可赋值的,而对是不可赋值的。但是,Pair Associative Container 可以提供不完全恒定的迭代器:使得表达式 (*i).second = d 有效的迭代器。

      【讨论】:

        【解决方案8】:

        第一

        Map 有一个重要的属性,即在 map 中插入一个新元素不会使指向现有元素的迭代器失效。从映射中删除元素也不会使任何迭代器失效,当然,实际上指向被删除元素的迭代器除外。

        二、下面的代码不错

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

        调用函数时,参数会在调用该函数之前进行计算。

        所以在调用erase之前计算iter++时,迭代器的++运算符将返回当前项,并在调用后指向下一项。

        【讨论】:

          【解决方案9】:

          恕我直言,没有 remove_if() 等价物。
          您无法重新排序地图。
          所以remove_if()不能把你的兴趣对放在最后,你可以打电话给erase()。

          【讨论】:

          • 这真的很不幸。
          【解决方案10】:

          基于 Iron Savior's answer 对于那些希望提供更多符合 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 项目并从迭代器中获取。

          【讨论】:

          • “以下划线后跟大写字母开头的标识符保留给实现的所有使用。”
          【解决方案11】:

          对于那些在 C++20 上的人,map 和 unordered_map 有内置的 std::erase_if 函数:

          std::unordered_map<int, char> data {{1, 'a'},{2, 'b'},{3, 'c'},{4, 'd'},
                                              {5, 'e'},{4, 'f'},{5, 'g'},{5, 'g'}};
           
          const auto count = std::erase_if(data, [](const auto& item) {
              auto const& [key, value] = item;
              return (key & 1) == 1;
          });
          

          【讨论】:

            【解决方案12】:

            Steve Folly's answer我觉得效率更高。

            这是另一个简单但效率较低的解决方案:

            该解决方案使用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);
            

            【讨论】:

            • 这似乎效率低下。
            【解决方案13】:

            如果你想擦除所有key大于2的元素,那么最好的方法是

            map.erase(map.upper_bound(2), map.end());
            

            但仅适用于范围,不适用于任何谓词。

            【讨论】:

              【解决方案14】:

              我是这样用的

               std::map<int, std::string> users;    
               for(auto it = users.begin(); it <= users.end()) {
                  if(<condition>){
                    it = users.erase(it);
                  } else {
                  ++it;
                  }
               }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2014-03-13
                • 2011-01-28
                • 2017-03-02
                • 2014-08-26
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多