c++boostdata-structuresstlbimap

C++ Bimap Left unordered_map Right sorted mutable multimap


I need to implement the following datastructure for my project. I have a relation of

const MyClass* 

to

uint64_t

For every pointer I want to save a counter connected to it, which can be changed over time (in fact only incremented). This would be no problem, I could simply store it in a std::map. The problem is that I need fast access to the pointers which have the highest values.

That is why I came to the conclusion to use a boost::bimap. It is defined is follows for my project:

typedef boost::bimaps::bimap<
        boost::bimaps::unordered_set_of< const MyClass* >,
        boost::bimaps::multiset_of< uint64_t, std::greater<uint64_t> >
> MyBimap;
MyBimap bimap;

This would work fine, but am I right that I can not modify the uint64_t on pair which were inserted once? The documentation says that multiset_of is constant and therefore I cannot change a value of pair in the bimap.

What can I do? What would be the correct way to change the value of one key in this bimap? Or is there a simpler data structure possible for this problem?


Solution

  • Here's a simple hand-made solution.

    Internally it keeps a map to store the counts indexed by object pointer, and a further multi-set of iterators, ordered by descending count of their pointees.

    Whenever you modify a count, you must re-index. I have done this piecemeal, but you could do it as a batch update, depending on requirements.

    Note that in c++17 there is a proposed splice operation for sets and maps, which would make the re-indexing extremely fast.

    #include <map>
    #include <set>
    #include <vector>
    
    struct MyClass { };
    
    
    struct store
    {
    
        std::uint64_t add_value(MyClass* p, std::uint64_t count = 0)
        {
            add_index(_map.emplace(p, count).first);
            return count;
        }
    
        std::uint64_t increment(MyClass* p)
        {
            auto it = _map.find(p);
            if (it == std::end(_map)) {
                // in this case, we'll create one - we could throw instead
                return add_value(p, 1);
            }
            else {
                remove_index(it);
                ++it->second;
                add_index(it);
                return it->second;
            }
        }
    
        std::uint64_t query(MyClass* p) const {
            auto it = _map.find(p);
            if (it == std::end(_map)) {
                // in this case, we'll create one - we could throw instead
                return 0;
            }
            else {
                return it->second;
            }
        }
    
        std::vector<std::pair<MyClass*, std::uint64_t>> top_n(std::size_t n)
        {
            std::vector<std::pair<MyClass*, std::uint64_t>> result;
            result.reserve(n);
            for (auto idx = _value_index.begin(), idx_end = _value_index.end() ;
                 n && idx != idx_end ;
                 ++idx, --n) {
                result.emplace_back((*idx)->first, (*idx)->second);
            }
            return result;
        }
    
    private:
        using map_type = std::map<MyClass*, std::uint64_t>;
        struct by_count
        {
            bool operator()(map_type::const_iterator l, map_type::const_iterator r) const {
                // note: greater than orders by descending count
                return l->second > r->second;
            }
        };
        using value_index_type = std::multiset<map_type::iterator, by_count>;
    
        void add_index(map_type::iterator iter)
        {
            _value_index.emplace(iter->second, iter);
        }
    
        void remove_index(map_type::iterator iter)
        {
            for(auto range = _value_index.equal_range(iter);
                range.first != range.second;
                ++range.first)
            {
                if (*range.first == iter) {
                    _value_index.erase(range.first);
                    return;
                }
            }
    
        }
    
        map_type _map;
        value_index_type _value_index;
    };