开发者

如何高效移除C++关联容器中的元素

目录
  • 一、简介
  • 二、移除给定位置的元素
  • 三、移除与特定键值等价的元素
  • 四、移除满足特android定条件的元素
    • 4.1、与序列容器的结构差异
    • 4.2、遵循接口
    • 4.3、迭代器操作
  • 五、总结

    一、简介

    关联容器将键与值关联起来,包括:

    • std::map,具有唯一键;
    • std::multimap,可以有几个相同的键;
    • std::unordered_map,具有唯一键的哈希映射;
    • std::unordered_multimap,可以有几个相同键的哈希映射。

    关联容器还包括集合(set):

    • std::set,包含唯一元素;
    • std::multiset,包含多个等价元素;
    • std::unordered_set,包含唯一元素的哈希集;
    • std::unordered_multiset,包含多个相同元素的哈希集。

    集合包含在关联容器中,因为它们可以被视为将键和值融合到一个元素中。

    二、移除给定位置的元素

    如果通过迭代器位置知道关联容器元素的位置(position),那么从关联容器中删除元素就非常容易。例如:

    // 移除该位置的条目。
    a.erase(position);
    
    // 删除第一个(包括在内)和最后一个(不包括在内)之间的所有元素。
    a.erase(first, last);
    

    这时候,指向被删除元素的迭代器失效,但指向容器的所有其他迭代器仍然有效。这是关联容器的不同之处。

    三、移除与特定键值等价的元素

    对于关联容器,不谈论“等于特定键值”,而是“等价于特定键值”。

    如果知道要移除的元素的键值,移除操作非常简单:

    a.erase(myKey);
    

    这将移除所有键值与 myKey 等价的元素(对于multi容器)。

    移除根据值而不是键值标识的元素:如果想移除一个 map (或其multi或哈希对应容器)中根据值而不是键值标识的元素,操作就不那么直观了。

    需要移除所有满足特定条件的元素,即它们的等于某个值。

    四、移除满足特定条件的元素

    4.1、与序列容器的结构差异

    为了根据特定条件移除序列容器中的元素,可以使用 std::remove_if。但在这里不能这样做。

    在序列容器中,将要保留的元素向上移动是可行的,因为它们的值只是按顺序排列的(这是序列容器的定义)。

    但关联容器有更强的约束:它们需要快速查找键值(对于非哈希容器,时间复杂度为 O(log(n));对于哈希容器,时间复杂度为 O(1))。为了达到这个目的,它们以更复杂的方式组织数据,通常非哈希容器使用树,而哈希容器使用表,其中精确的位置很重要。

    因此,不能像 std::remove_if 那样简单地重新排列元素,否则会破坏内部结构。所以必须遵循接口。而接口中提供的是上面看到的 erase 方法。

    4.2、遵循接口

    移除满足特定条件的元素的一般思路是遍历容器,对每个元素检查条件,并移除返回 true 的元素。但问题是如何在遍历的同时移除元素?

    考虑一下这种遍历的朴素版本:

    template<typename AssociativeContainer, typename Predicate>
    void erase_if(AssociativeContainer& container, Predicate shouldRemove)
    {
        for (auto it = begin(container); it != end(container); ++it) {
            if (shouldRemove(*it)) {
                container.erase(it);
            }
        }
    }
    

    注意,这是一种非常罕见的情况,在这种情况下,对迭代器所知不多,只知道它们是迭代器。这是永远不应该出现的代码。

    看看上面示例的这一行代码:

    container.erase(it);
    

    这会使 it 失效。然后看for循环的结尾位置:

    for (auto it = begin(container); it != end(container); ++it)
    

    在 it 失效后立即执行 ++it。这会导致未定义行为。

    4.3、迭代器操作

    需要找到一种方法,在移除元素之前递增迭代器。为此,有几种选择。在 C++98 中,可以使用后缀递增运算符,它将首先递增迭代器,然后将未递增迭代器的副本传递给 erase

    templ编程ate<typename AssociativeContainer, typename Predicate>
    void erase_if(AssociativeContainer& container, Predicate shouldRemove)
    {
        for (auto it = begin(container); it != end(container);) {
            if (shouldRemove(*it))
                container.erase(it++);
            else
                ++it;
        }
    }
    

    但操作迭代器的危险行同样非常高。在 C++11 中,得到了一个风险更小的实现,因为 erase 返回移除元素后的迭代器。可以用这种方式重写代码:

    template<typename AssociativeContainer, typename Predicate>
    void erase_if(AssociativeContainer& container, Predicate shouldRemove)
    {
        for (auto it = begin(container); it != end(container);) {
            if (shouldRemove(*it))
                it = container.erase(it);
            else
                ++it;
        }
    }
    

    为了确保此函数仅用于关联容器,C++标准js应该出现相关概念,但在此之前,可以显式地编写各种情况:

    namespace details
    {
        template<typename AssociativeContainer, typename Predicate>
        void erase_if_impl(AssociativeContainer& container, Predicate shouldRemove)
        {
            for (auto it = begin(container); it != end(container); /* nothing here, the increment in dealt with inside the loop */ )
            {
                if (shouldRemove(*it))
                {
                    it = container.erase(it);
                }
                else
                {
                    ++it;
                }
            }
        }
    }
    
    template<typename Key, typename Value, typename Comparator, typename Predicate>
    void erase_if(std::map<Key, Value, Comparator>& container, Predicate shouldRemove)
    {
        return details::erase_if_impl(container, shouldRemove);
    }
    
    template<typename Key, typename Value, typename Comparator, typename Predicate>
    void erase_if(std::multimap<Key, Value, Comparator>& container, Predicate shouldRemove)
    {
        return details::erase_if_impl(container, shouldRemove);
    }
    
    template<typename Key, typename Value, typename Comparator, typename Predicate>
    void erase_if(std::unordered_map<Key, Value, Comparator>& container, Predicate shouldRemove)
    {
        return details::erase_if_impl(container, shouldRemove);
    }
    
    template<typename Key, typename V编程alue, typename Comparator, typename Predicate>
    void erase_if(std::unordered_multimap<Key, Value, Comparator>& container, Predicate shouldRemove)
    {
        return details::erase_if_impl(container, shouldRemove);
    }
    
    template<typename Key, typename Comparator, typename Predicate>
    void erase_if(std::set<Key, Comparator>& container, Predicate shouldRemove)
    {
        return details::erase_if_impl(container, shouldRemove);
    }
    
    template<typename Key, typename Comparator, typename Predicate>
    void erase_if(std::multiset<Key, Comparator>& container, Predicate shouldRemove)
    {
        return details::erase_if_impl(container, shouldRemove);
    }
    
    template<typename Key, typename Comparator, typename Predicat编程客栈e>
    void erase_if(std::unordered_set<Key, Comparator>& container, Predicate shouldRemove)
    {
        return details::erase_if_impl(container, shouldRemove);
    }
    
    template<typename Key, typename Comparator, typename Predicate>
    void erase_if(std::unordered_multiset<Key, Comparator>& container, Predicate shouldRemove)
    {
        return details::erase_if_impl(container, shouldRemove);
    }
    

    五、总结

    • 移除给定位置的元素: 使用 erase(position) 或 erase(first, last) 方法,可以移除指定位置的元素或指定范围内的元素。
    • 移除与特定键值等价的元素: 使用 erase(myKey) 方法,可以移除所有键值与 myKey 等价的元素。
    • 移除满足特定条件的元素: 由于关联容器的内部结构,无法直接使用 std::remove_if 方法移除满足特定条件的元素。需要使用迭代器并手动遍历容器,检查每个元素是否满足条件,并使用 erase 方法移除满足条件的元素。

    在移除元素时,需要注意迭代器失效的问题,并使用正确的迭代器操作方式来避免未定义行为。

    本文还提供了 erase_if 函数的实现,该函数可以用于移除关联容器中满足特定条件的元素。该函数使用 erase 方法和迭代器操作来实现,并针对不同的关联容器类型进行了重载。

    以上就是如何高效移除C++关联容器中的元素的详细内容,更多关于移除C++关联容器元素的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜