简体   繁体   中英

How to use std::unordered_map with 'external' keys

My goal is to write a class that works like a unordered_map but keeps the insertion order of the elements while still allowing O(1) lookup by key.

My approach is as follows:

// like unordered_map but keeps the order of the elements for iteration
// implemented as a vector with a unordered_map for constant time lookups
// consider if element removal is needed, since it is O(N) because of the use of a vector of elements
template <typename KEY, typename VAL>
struct ordered_map {
    struct KeyValue {
        KEY key;
        VAL value;
    };

    std::vector<KeyValue> elements; // KeyValue pairs in order of insertion to allow iteration
    std::unordered_map<KEY, int> indexmap; // key -> index map to allow O(1) lookup, is there a way to avoid the redundant key?
    
    //...
}

But I have the problem that in my approach I want to lookup into the indexmap using the keys which are stored 'externally' to it (in the elements vector based on the index value in the map).

std::sort for example allows passing in a comparator, but unordered_sap does not seem to have anything similar.

I could not really find anything online about how to accomplish this, but I might be searching with the wrong terms.

I this approach at all supported by the stl?

Or do I need to resort to storing the Key twice , which I would like to avoid as keys can be heap objects like std::strings for example.

EDIT: unordered_map instead of unordered_set which does not work

My goal is to write a class that works like a unordered_map but keeps the insertion order of the elements while still allowing O(1) lookup by key

... and without duplicating the key, in cases it's large and/or expensive.

So, you want two things:

  1. a constant-time associative lookup with the same semantics as std::unordered_map (no duplicate keys, or you would/should have asked for std::unordered_multimap )
  2. a sequential index tracking insertion order

You have chosen to implement the associative lookup using a sequential container, and the sequential index using an associative container. I don't know why, but let's try the more natural alternative:

  1. the associative lookup should just be std::unordered_map<Key, SomeValueType>
  2. the sequential index can just be std::vector<SomeValueType*>

The remaining blank is SomeValueType : it could just be VAL , but then we need to do extra work to fix the sequential index whenever we erase something. We could make it std::pair<VAL, size_t> instead, so it can store the index i of the iterator we'll need to remove on erasure. The down side is that on top of moving all i+1..N vector elements down one, we also need to update the index value for each of those map elements.

If you want to preserve constant-time erasure, you probably need the sequential index to be a std::list<SomeValueType*> , and to keep a std::list::iterator in your map element. Actual linear iteration will be slower on a linked list than a vector, but you get the same complexity when erasing elements.


NB. The sequential index does need to store pointers rather than iterators - I originally forgot the invalidation behaviour for unordered maps. However, if you want access to the key, they can obviously be std::unordered_map<key, SomeValueType>::value_type pointers... I just wrote out the shorter alternative.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM