如何在一个集合中插入新值并同时擦除另一个值?

时间:2014-10-07 23:04:11

标签: c++ c++11 stl set

每个集合包含指定顺序的元素。我想指定一个集合大小的界限,并且自动删除最后一个元素,如果插入一个严格较小的新单元(就顺序而言)并且已达到指定的大小。

当然,我可以做类似以下的事情:

class bounded_set
{
private:
    using set = std::set<Key, Compare, Allocator>;
    using iterator = typename set::iterator;

public:
    bounded_set(std::size_t size)
        : m_size(size)
    { }

    std::pair<iterator, bool> insert(Key const& value)
    {
        if (m_set.size() < m_size)
            return m_set.insert(value);

        auto last = std::prev(m_set.end());
        if (Compare()(value, *last))
        {
            m_set.erase(last);
            return m_set.insert(value);
        }
        return std::make_pair(last, false);
    }

private:
    set m_set;
    std::size_t m_size;
};

除了事实之外,bounded_set不是最好的名字(因为有界容器在并发编程领域是众所周知的事情),我担心内存在这个实现中分配。最有可能的是,首先,last使用的空间将被释放。但在此之后,需要为value分配新的内存。

我真正想要做的是,使用为last分配的内存,并将value的数据复制到此处,同时保留订单。

2 个答案:

答案 0 :(得分:2)

如果我正确理解您的问题,取决于基础数据结构的工作原理,如果不必编写自定义内存分配器或使用库中的一个,则不一定能够实现。例如,std::set使用红黑树作为底层数据结构。因此,节点的存储器位置以及与这些节点相关的关系指针固有地附加到树的总顺序。您无法从最少&#34;的节点重新使用内存。价值并在那里放置另一个值,这不是一个新的完全有序的&#34;最少&#34;值而不重新排序指向该节点的所有指针,以便它在树中适当位置以获取该节点的值。

如果您仍然关注内存使用情况并希望坚持使用STL而不是std::set,那么您可能应该研究一个固定长度的优先级队列或者那种使用基于数组的堆作为底层数据结构,因此不会为新节点不断分配和重新分配内存。

答案 1 :(得分:2)

我看到了几个选项,标准委员会失去了很容易解决问题的机会。

N3586提出了解决问题的方法。

std::pair<iterator, bool> insert(Key const& value)
{
    if (m_set.size() < m_size)
        return m_set.insert(value);

    auto last = std::prev(m_set.end());
    if (Compare()(value, *last))
    {
        auto temp = m_set.remove(last);
        *temp = value;
        return m_set.insert(temp);
    }
    return std::make_pair(last, false);
}

在此假设的重写中,tempnode_ptr,允许非const访问节点的value_type。您可以删除节点,写入节点并重新插入节点,而无需为节点分配任何内容。

委员会礼貌地拒绝了这个提议。

std::set的自定义分配器可以以不太优雅的方式完成这一操作。这样的分配器只会缓存节点,而现有的insert只会起作用。这种方法的一个小缺点是,虽然自定义分配器可以防止您的节点被解除分配,但是当您更改它时,它不能阻止您的Key被破坏,然后构建。某些类型在分配方面比在销毁构建周期中更有效。有时候前者可能是noexcept,而后者则不是。{/ p>

总之,我将自定义分配器方法视为最后的手段。你可以使它工作。但它需要一些精心策划和非直观的代码。

我想到了使用push_heappop_heap。但是,如果你确实需要一个返回插入或相等元素的迭代器,它的使用会很尴尬。如果你可以处理void返回类型,它可能看起来像:

void insert(Key const& value)
{
    if (m_set.size() < m_size)
    {
        m_set.push_back(value);
        std::push_heap(m_set.begin(), m_set.end(), Compare{});
    }

    if (Compare()(value, m_set.front()))
    {
        std::pop_heap(m_set.begin(), m_set.end(), Compare{});
        m_set.back() = value;
        std::push_heap(m_set.begin(), m_set.end(), Compare{});
    }
}

但是在堆中搜索新插入的值很尴尬,push_heap没有提供此信息。

另一个选项是有序矢量+插入排序。您必须自己编写插入排序,但这是一个相对较小的编程任务。您希望插入排序的原因是您将始终排序除最后一个元素之外的已排序数组。插入排序是这项工作的最佳选择。

这些解决方案都不是完美的,除了N3586之外没有任何解决方案可以提供任何接近解决方案的东西#34;开箱即用#34;即不需要超过少数几行的解决方案代码而N3586并不存在。如果您认为它应该存在,请联系您的C ++国家机构代表,并告诉他们。或者自己参与C ++委员会,然后游说吧。