类似deque的数据结构,在任意位置具有快速查找和删除功能

时间:2016-12-01 17:21:28

标签: c++ data-structures deque

我正在寻找可以解决以下用例的数据结构:

  • 从后面插入值
  • 单个值的大小是几十个字节。
  • 值通过其中一个字段按升序自然排序,该服务器作为唯一标识符。
  • 值通常从前面删除,但也可以从键指定的任意位置删除,因此查找和删除应该很快。
  • 将连续的值子集复制到此类型的新数据结构应该很便宜。
  • 清算应该便宜。
  • 通常包含数十或数百个值,但也有数千个值。
  • 性能应该是一致的,因为我将其用于软实时系统。

我一直在思考一个deque +一个带有位图的辅助双端队列,可以用来指示删除值,但在我坐下来编写代码之前,我很感激你的建议。谢谢!

4 个答案:

答案 0 :(得分:1)

您可以尝试使用unordered_mapkey_type模板参数的链接node<value_type>,该节点将具有上一个\ next values'键。

该类与此类似:(意思是不完整)

#include <unordered_map>

template<typename key, typename value>
struct linked_map {

    void push_back(key key_, value value_) {
        if (!is_first_last_set)
            first_last.first = key_;
        assert(base.find(key_) == base.end());
        base[key_] = value_;
        // TODO: set prev/next_node_key
        first_last.second = key_;
        is_first_last_set = true;
    }
    void erase(key key_) {
        // TODO: update previous and next node's previous and next keys
        base.erase(base.find(key_));
    }
    value &front() {
        return base[first_last.first].data;
    }
    void pop_front() {
        erase(first_last.first);
    }
    ...

    bool is_first_last_set = false;
    std::pair<key,key> first_last;
    struct node {
        value data;
        key prev_node_key,next_node_key;
    };
    std::unordered_map<key,std::pair<key,value>> base;
};

unordered_mapO(1)随机访问以进行删除。 值中的node是将订单保存在unordered_map

我选择使用unordered_map,因为它具有比map更一致的性能,因为它不必分配(分配可能需要更长时间内存碎片或无论什么原因)插入,并且处理器必须做的最大值是插入\ delete \ front是一些缓存未命中。

答案 1 :(得分:1)

我保持简单。

要在中间处理元素删除,我会使用optional<T>。清洁比单独的位设置。与单独的bitset相比,内存成本适中,但连续的内存似乎是值得的。

由于您有键值,我会使用pair< key, optional<value> >,如果删除该值,我会单独保留密钥。这使搜索代码更容易。

对于双头关系,deque开始。这不是理想的,但它是写的。一个双端圆形矢量可能会更快,但我很懒。

答案 2 :(得分:0)

我会将数据结构编写为具有适当方法的类。作为第一步,将其实现为您能想到的最简单的事情。 (可能是一个有序的矢量)。

然后你可以去实现应用程序的其余部分,然后你可以看到数据结构是否是一个瓶颈,如果是,那么优化它。 (到那时,您将拥有一个功能齐全的应用程序,您可以使用它来测试不同的可能实现。)

答案 3 :(得分:0)

在考虑了这些建议之后,我选择了一个具有以下属性的双端队列:

  • 侵入:这些值提供了一个可以搜索它们的键,以及将它们标记为已删除的方法,以及检查它们是否被删除。
  • 已排序:值按键的升序存储。
  • 前后元素始终未删除。

我已将代码推送到GitHub - https://github.com/yitzikc/InstrusiveSortedDeque

它也可以在下面找到:

/*
 * InstrusiveSortedDeque.h
 *
 *  Created on: Dec 4, 2016
 *      Author: yitzikc
 */

#ifndef UTILS_INTRUSIVESORTEDDEQUE_H_
#define UTILS_INTRUSIVESORTEDDEQUE_H_

#include <algorithm>
#include <deque>

#include <boost/iterator/filter_iterator.hpp>

namespace Utils {

// InstrusiveSortedDeque: A deque containing sorted values, which should be default constructible and supply the following types and methods:
// * typedef KeyType
// * KeyType GetKey() const;
// * IsDeleted() const; - indicating that a value should be considered as removed
// * Remove()           - Designates a value as deleted.

template <typename T>   // TODO: Add other template args allowing the allocator to be customized
class InstrusiveSortedDeque : public std::deque<T> {
private:

    typedef std::deque<T> StdDeque;

    static constexpr bool FilterPredicate(const T& value) { return ! value.IsDeleted(); }


    // A default-constructible type which wraps FilterPredicate
    struct FilterPredicateType {
        inline bool operator() (const T& value)
        {
            return FilterPredicate(value);
        }
    };

public:

    using typename StdDeque::allocator_type;
    using typename StdDeque::size_type;
    using typename StdDeque::pointer;
    using typename StdDeque::const_pointer;
    using typename StdDeque::reference;
    using typename StdDeque::const_reference;

    // A user-supplied key type
    typedef typename T::KeyType key_type;
    typedef T value_type;

    // A key-type supporting quick access. Essentially a thin wrapper around indexes of the underlying deque class
    class quick_key_type {
        int m_index;

        enum { INVALID_INDEX = -1, MIN_VALID_INDEX = 0 };

        friend class InstrusiveSortedDeque;

        explicit constexpr quick_key_type(int index = INVALID_INDEX)
            : m_index(index)
        {
        }

    public:
        constexpr quick_key_type(const quick_key_type&) = default;
        BOOST_CXX14_CONSTEXPR quick_key_type& operator=(const quick_key_type&) = default;
        constexpr bool is_valid() const { return m_index >= MIN_VALID_INDEX; }
        constexpr bool is_front() const { return m_index == MIN_VALID_INDEX; }
        constexpr bool operator==(quick_key_type other) { return other.m_index == m_index; }
        constexpr bool operator< (quick_key_type other) { return other.m_index <  m_index; }
        ~quick_key_type() = default;
    };

    typedef boost::filter_iterator<FilterPredicateType, typename StdDeque::iterator> iterator;
    typedef boost::filter_iterator<FilterPredicateType, typename StdDeque::const_iterator> const_iterator;
    typedef boost::filter_iterator<FilterPredicateType, typename StdDeque::reverse_iterator> reverse_iterator;
    typedef boost::filter_iterator<FilterPredicateType, typename StdDeque::const_reverse_iterator> const_reverse_iterator;

    InstrusiveSortedDeque( const_iterator first, const_iterator last, const allocator_type& alloc = allocator_type() )
        : StdDeque(first, last, alloc)
        , m_nMarkedAsErased(0)
    {
    }

    InstrusiveSortedDeque( iterator first, iterator last, const allocator_type& alloc = allocator_type() )
        : StdDeque(first, last, alloc)
        , m_nMarkedAsErased(0)
    {
    }

    InstrusiveSortedDeque(const InstrusiveSortedDeque<T>& other)
        : InstrusiveSortedDeque(other.cbegin(), other.cend(), other.get_allocator())
// FIXME: Aliasing the allocator might not be a good idea when we use custom allocators
    {
    }

    template< class InputIt >
    InstrusiveSortedDeque( InputIt first, InputIt last, const allocator_type& alloc = allocator_type() )
        : StdDeque(MakeFilteredIter(this, first), MakeFilteredIter(this, last), alloc)
        , m_nMarkedAsErased(0)
    {
    }

    InstrusiveSortedDeque()
        : StdDeque::deque()
        , m_nMarkedAsErased(0)
    {
    }

    using StdDeque::deque;

    InstrusiveSortedDeque<T>& operator=(const InstrusiveSortedDeque<T>& other)
    {
        Clone(other);
        return *this;
    }

    InstrusiveSortedDeque<T>& operator=(InstrusiveSortedDeque<T>&& other)
    {
        static_cast<StdDeque*>(this)->operator=(other);
        m_nMarkedAsErased = other.m_nMarkedAsErased;
        return *this;
    }

    // Accessors by quick_key
    reference at(quick_key_type key)
    {
        return GetByQuickKey<reference>(this, key);
    }

    const_reference at(quick_key_type key) const
    {
        return GetByQuickKey<const_reference>(this, key);
    }

    reference operator[](quick_key_type key)
    {
        return GetByQuickKey<reference>(this, key);
    }

    const_reference operator[](quick_key_type key) const
    {
        return GetByQuickKey<const_reference>(this, key);
    }

    // Methods for obtaining forward iterators

    iterator begin()
    {
        ValidateEdge(this->front());
        return MakeFilteredIter(this, StdDeque::begin());
    }

    const_iterator begin() const
    {
        ValidateEdge(this->front());
        return MakeFilteredIter(this, StdDeque::begin());
    }

    const_iterator cbegin() const
    {
        return begin();
    }

    iterator end()
    {
        ValidateEdge(this->back());
        return MakeFilteredIter(this, StdDeque::end());
    }

    const_iterator end() const
    {
        ValidateEdge(this->back());
        return MakeFilteredIter(this, StdDeque::end());
    }

    const_iterator cend() const
    {
        return end();
    }

    // Methods for obtaining reverse iterators

    reverse_iterator rbegin()
    {
        ValidateEdge(this->back());
        return MakeFilteredIter(this, StdDeque::rbegin());
    }

    const_reverse_iterator rbegin() const
    {
        ValidateEdge(this->back());
        return MakeFilteredIter(this, StdDeque::rbegin());
    }

    const_reverse_iterator crbegin() const
    {
        return rbegin();
    }

    reverse_iterator rend()
    {
        ValidateEdge(this->front());
        return MakeFilteredIter(this, StdDeque::rend());
    }

    const_reverse_iterator rend() const
    {
        ValidateEdge(this->front());
        return MakeFilteredIter(this, StdDeque::rend());
    }

    const_reverse_iterator crend() const
    {
        return end();
    }

    iterator quick_key_to_iterator(quick_key_type qk)
    {
        return QuickKeyToIterator(this, qk);
    }

    const_iterator quick_key_to_iterator(quick_key_type qk) const
    {
        return QuickKeyToIterator(this, qk);
    }

    size_type size() const
    {
        const size_type baseSize = capacity();
        if (baseSize <= 1) {
            assert(0 == m_nMarkedAsErased);
            return baseSize;
        }

        const ssize_t netSize = baseSize - m_nMarkedAsErased;
        assert(netSize > 1);
        return netSize;
    }

    // Size of the underlying deque
    size_type capacity() const
    {
        return StdDeque::size();
    }

    // Find methods which return an iterator to the specified key using a binary search
    const_iterator find(key_type k) const
    {
        typename StdDeque::const_iterator it;
        DoFind(this, it, StdDeque::cbegin(), StdDeque::cend(), k);
        return MakeFilteredIter(this, std::move(it));
    }

    // Find methods which return an iterator to the specified key using a binary search
    iterator find(key_type k)
    {
        typename StdDeque::iterator it;
        DoFind(this, it, StdDeque::begin(), StdDeque::end(), k);
        return MakeFilteredIter(this, std::move(it));
    }

    // An alternate find, starts by searching at the front of the deque before trying the usual search,
    // and returns a 'quick key' instead of an iterator
    quick_key_type find_front(key_type userKey) const
    {
        if (! this->empty()) {
            if (this->front().GetKey() == userKey) {
                return quick_key_type(quick_key_type::MIN_VALID_INDEX);
            }
            else {
                // TODO: cache the result that we find here, and try searching from the cached value.
                typename StdDeque::const_iterator it;
                if (DoFind(this, it, StdDeque::cbegin(), StdDeque::cend(), userKey)) {
                    return quick_key_type(it - StdDeque::cbegin());
                }
            }
        }

        return quick_key_type();
    }

    void erase(iterator& it)
    {
        erase(it.base());
        return;
    }

    // TODO: Also implement the overload using a range.

    bool erase(key_type k)
    {
        typename StdDeque::iterator it;
        if (DoFind(this, it, StdDeque::begin(), StdDeque::end(), k)) {
            return erase(it);
        }

        return false;
    }

    bool erase(quick_key_type k)
    {
        return erase(StdDeque::begin() + k.m_index);
    }

    void pop_front()
    {
        assert(! this->empty() && ! this->front().IsDeleted());
        StdDeque::pop_front();
        TrimFront();
    }

    void pop_back()
    {
        assert(! this->empty() && ! this->back().IsDeleted());
        StdDeque::pop_back();
        TrimBack();
    }

    void clear()
    {
        StdDeque::clear();
        m_nMarkedAsErased = 0;
        return;
    }

    void assign(const_iterator first, const_iterator last)
    {
        AssignFiltered(first, last);
    }

    void assign(iterator first, iterator last)
    {
        AssignFiltered(first, last);
    }

    template< class InputIt >
    void assign( InputIt first, InputIt last )
    {
        AssignFiltered(MakeFilteredIter(this, first), MakeFilteredIter(this, last));
    }

    // Wrappers for emplace_back() and emplace_front(), which return references to the newly created values
    // Note that this is incompatible with the C++ 17 interface, where emplace...() methods return iterators

    // A more flexible emplace_back which will attempt to emplace at the back but will insert at the correct position
    // if the new value is not in fact greater than the last value
    template< typename... Args >
    reference emplace_back(Args&&... args)
    {
        const_pointer const prevBack = this->empty() ? nullptr : & (this->back());
        StdDeque::emplace_back(std::forward<Args>(args)...);
        reference& back = this->back();
        assert(! back.IsDeleted());
        if (nullptr != prevBack) {
            assert(! prevBack->IsDeleted());
            if (BOOST_UNLIKELY(back.GetKey() <= prevBack->GetKey())) {
                assert(back.GetKey() < prevBack->GetKey());
                auto it = DoFindUnchecked(StdDeque::begin(), StdDeque::end() - 1, back.GetKey());
                assert((it->GetKey() > back.GetKey()) && (& *it != &back));
                auto newIt = StdDeque::emplace(it, std::move(back));
                StdDeque::pop_back();
                ValidateEdge(this->back());
                return *newIt;
            }
        }

        return back;
    }

    // TODO: Handle out-of-place values in emplace_front like in emplace_back
    template< typename... Args >
    reference emplace_front(Args&&... args)
    {
        const_pointer const prevFront = this->empty() ? nullptr : & (this->front());
        StdDeque::emplace_front(std::forward<Args>(args)...);
        assert(! this->front().IsDeleted());
        assert( (nullptr == prevFront) ||
                ((! prevFront->IsDeleted()) && (this->front().GetKey() < prevFront->GetKey())));

        return this->front();
    }

    // FIXME: Implement resize() to  to maintain the invariants for m_nMarkedAsErased if it shrinks
    // FIXME: Implement the other overloads of assign() to maintain the invariants for m_nMarkedAsErased

private:
    typename StdDeque::size_type m_nMarkedAsErased = 0;

    void TrimFront()
    {
        while (! this->empty() && this->front().IsDeleted()) {
            StdDeque::pop_front();
            --m_nMarkedAsErased;
        }
        return;
    }

    void TrimBack()
    {
        while (! this->empty() && this->back().IsDeleted()) {
            StdDeque::pop_back();
            --m_nMarkedAsErased;
        }
        return;
    }

    template <typename ThisType, typename IterType>
    static bool DoFind(ThisType thisPtr, IterType& result, IterType&& beginIter, IterType&& endIter, key_type k)
    {
        if (! thisPtr->empty() && (k <= thisPtr->back().GetKey())) {
            result = DoFindUnchecked(beginIter, endIter, k);
            // FIXME: We should handle cases when endIter != StdDeque::end()
            assert(result != thisPtr->StdDeque::end());
            if ((result->GetKey() == k) && (endIter != result)) {
                return true;
            }
        }

        result = thisPtr->StdDeque::end();
        return false;
    }

    template <typename IterType>
    static inline auto DoFindUnchecked(IterType&& beginIter, IterType&& endIter, key_type k)
    {
        constexpr auto FindComp = [](const T& value, typename T::KeyType k)->bool { return value.GetKey() < k; };
        return std::lower_bound(beginIter, endIter, k, FindComp);
    }

    void Clone(const InstrusiveSortedDeque& other)
    {
        StdDeque::resize(other.size());     // Pre-allocate space if necessary
        constexpr auto pred = [](const value_type& r) { return ! r.IsDeleted(); };
        std::copy_if(other.StdDeque::begin(), other.StdDeque::end(), StdDeque::begin(), pred);
        m_nMarkedAsErased = 0;
    }

    template <typename RefType, typename ThisType>
    static inline RefType GetByQuickKey(ThisType thisPtr, quick_key_type key)
    {
        return thisPtr->StdDeque::at(key.m_index);
    }

    template <typename ThisType>
    static auto QuickKeyToIterator(ThisType thisPtr, const quick_key_type qk)
    {
        if (qk.is_valid()) {
            auto it = thisPtr->StdDeque::begin() + qk.m_index;
            if (! it->IsDeleted()) {
                return MakeFilteredIter(thisPtr, std::move(it));
            }
        }

        return MakeFilteredIter(thisPtr, thisPtr->StdDeque::end());
    }

    // Filter iterator wrapping implementation
    template <typename ThisType, typename BaseIterType>
    inline static boost::filter_iterator<FilterPredicateType, BaseIterType> MakeFilteredIter(ThisType thisPtr, BaseIterType&& baseIter)
    {
        return boost::make_filter_iterator<FilterPredicateType>(baseIter, thisPtr->StdDeque::end());
    }

    template< class InputIt >
    void AssignFiltered(InputIt first, InputIt last)
    {
        StdDeque::assign(first, last);
        m_nMarkedAsErased = 0;
    }

    // Validate that a value is a valid fron or back value
    void ValidateEdge(const value_type& v) const
    {
        // Note that if the deque is empty, the reference to v might be garbage.
        assert(this->empty() || ! v.IsDeleted());
    }

    bool erase(typename StdDeque::iterator it)
    {
        if (! it->IsDeleted()) {
            it->Remove();
            assert(it->IsDeleted());
            ++m_nMarkedAsErased;
            TrimFront();
            TrimBack();
            return true;
        }
        else {
            assert((capacity() > 1) && (m_nMarkedAsErased > 0));
            ValidateEdge(this->front());
            ValidateEdge(this->back());
            return false;
        }
    }
};

}   // namespace Utils

#endif /* UTILS_INTRUSIVESORTEDDEQUE_H_ */