【问题标题】:Erase specific elements in std::map擦除 std::map 中的特定元素
【发布时间】:2011-10-23 21:09:58
【问题描述】:

我想删除我的 std::map 中的一些元素。
我编写了擦除 + remove_if 技术,我总是使用其他序列容器。
但它不是用地图编译的。为什么?
我该如何做这份工作?

std::map<int, int> m;

bool foo(const std::pair<int, int>& p)
{
    return p.second > 15;
}

int _tmain(int argc, _TCHAR* argv[])
{
    m.insert(make_pair(0, 0));
    m.insert(make_pair(1, 10));
    m.insert(make_pair(2, 20));
    m.insert(make_pair(3, 30));

    m.erase(
        remove_if(m.begin(), m.end(), foo),
        m.end()); // compile error

    return 0;
}

【问题讨论】:

标签: c++ algorithm stl map containers


【解决方案1】:

为 map 写成这样,因为 remove_if 不适用于 map 迭代器(它只是将违规元素放在末尾,而 map 迭代器不允许这样做):

template <typename Map, typename F>
void map_erase_if(Map& m, F pred)
{
    typename Map::iterator i = m.begin();
    while ((i = std::find_if(i, m.end(), pred)) != m.end())
        m.erase(i++);
}

或者如果你喜欢单行:

template <typename Map, typename F>
void map_erase_if(Map& m, F pred)
{
    for (typename Map::iterator i = m.begin();
         (i = std::find_if(i, m.end(), pred)) != m.end();
         m.erase(i++));
}

【讨论】:

  • 这不起作用,erase 迭代器可能会失效。
  • @Kerrek:实际上我不喜欢它,而且我从不写这样的代码。当写成一个while循环时,一个空体的for循环几乎总是更清晰。
  • 嗯,这当然不是最容易阅读的代码,但它是一个很酷的演示,展示了您可以使用 for 表达式做什么,它使 i 成为循环范围的本地变量,没有多余的大括号。就个人而言,我想我会在结尾写 {} 而不是分号。
【解决方案2】:

“使用其他序列容器”是您的错误 - map 是一个 关联 容器!在关联容器中,元素由它们的键定义(与它们在序列容器中的插入顺序相反),您可以按键删除元素:

m.erase(12);

按键值擦除与查找具有相同的复杂性(例如,O(log n) 用于映射,O(1) 用于无序映射等)。或者,您可以在恒定时间内通过迭代器擦除。擦除迭代器会使该迭代器无效,但不会使其他迭代器无效(再次与序列容器不同),因此如果您想迭代地图,典型的习惯用法是这样的:

for (auto it = m.cbegin(); it != m.cend(); ) // no "++"!
{
  if (it->second > 15)  // your own condition goes here
  {
    m.erase(it++);
  }
  else
  {
    ++it;
  }
}

【讨论】:

    【解决方案3】:

    因为std::map 不是“序列容器”:) remove_if 会尝试将无用的元素放在地图的末尾,但这会导致违反地图的隐式数据结构(大多数情况下是红黑树)。隐式数据结构定义了地图中每个元素的位置,这就是为什么 remove_if 不允许用于 std::map

    您应该在循环中一个接一个(或给出一些间隔)从std::map 中删除元素。

    类似这样的:

    it = m.begin();
    while ((it = std::find_if(it, m.end(), pred)) != m.end())
        m.erase(it++);
    

    【讨论】:

    • 从地图中擦除不会使除被擦除对象之外的迭代器无效。
    • @Kerrek SB 我的意思是在erase(it) it 失效后,erase(it++) 应该用作@Alexandre C 的答案。
    • 好吧,但这不是你要说的。您说的是“删除元素后,map 的迭代器变得无效”,这非常具有误导性。
    • @Alex:哦,对不起,没关系!我忽略了查找仅从最后找到的位置开始。我删除了评论。 Mirhan:很抱歉,it++ 必要的。确保将其初始化为begin(),不过:-)
    • 与 Kerrek SB 或 Nim 的解决方案相比,它的可比性太多了。我误会了吗?
    【解决方案4】:

    这个习语只适用于像容器这样的序列 - 地图(关联)中的条目不能重新排序(键不会改变 - 所以你怎么可能期望移动一个条目到其他位置 - 例如结束)。正确的方法是找到条目并将其删除 - 即it = map.find(); map.erase(it++)

    【讨论】:

    • 他没有尝试通过键删除条目;他正在根据任意函数删除它们。
    • @Nicol Bolas:是的,我是。但我正在重新考虑我应该使用的容器。为什么我在 std::map 中按值搜索? -_-a 无论如何,答案是正确的。谢谢大家。
    【解决方案5】:

    试试这样的

    #include <iostream>
    #include <map>
    #include <algorithm>
    
    class foo 
    {
    public:
        enum CompType { GREATER=1, LESS=-1 };
        foo(int nVal=15, enum CompType ctype=GREATER)
        : m_nVal(nVal)
        , m_bGreater(ctype==GREATER)
        {
        }
        bool operator()(std::pair<int, int> p) 
        {
            if (m_bGreater)
                return p.second > m_nVal;
            else
                return p.second < m_nVal;
        }
    private:
        int  m_nVal;
        bool m_bGreater;
    };
    
    void MapRemove(std::map<int, int> &m, foo &pred)
    {
        auto itr = std::find_if(m.begin(), m.end(), pred);
        while (itr != m.end())
            itr = std::find_if(m.erase(itr), m.end(), pred);
    }
    
    int main(int argc, char *argv[])
    {
        std::map<int, int> m;
    
        m.insert(std::make_pair(0, 0));
        m.insert(std::make_pair(1, 10));
        m.insert(std::make_pair(2, 20));
        m.insert(std::make_pair(3, 30));
    
        MapRemove(m, foo());
    
        for (auto itr=m.begin(); itr!=m.end(); ++itr)
            std::cout << "(" << itr->first << ", " 
                      << itr->second << ")" << '\n';
    
        return 0;
    }
    

    【讨论】:

      【解决方案6】:

      但它不是用地图编译的。为什么?

      使用remove_if时,解引用迭代器的类型必须满足CopyAssignable的要求。也就是说应该可以将一个值分配给另一个值。

      对于std::map&lt;int, int&gt;,值为std::pair&lt;const int, int&gt;,表示映射的键值对,不可复制。这个const int for key 的原因是地图在内部是如何工作的,正如其他人已经指出的那样。

      顺便说一下,你会得到同样的序列容器编译错误,像这样:

      std::vector<std::pair<const int, int>>;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-09-12
        • 1970-01-01
        • 2022-10-24
        • 1970-01-01
        • 2011-10-24
        • 2011-09-30
        • 1970-01-01
        • 2016-03-31
        相关资源
        最近更新 更多