// This code is in the public domain -- Ignacio Castaņo #pragma once #ifndef NV_CORE_HASHMAP_H #define NV_CORE_HASHMAP_H /* HashMap based on Thatcher Ulrich container, donated to the Public Domain. I'd like to do something to reduce the amount of code generated with this template. The type of U is largely irrelevant to the generated code, except for calls to constructors and destructors, but the combination of all T and U pairs, generate a large amounts of code. HashMap is not used in NVTT, so it could be removed from the repository. */ #include "Memory.h" #include "Debug.h" #include "ForEach.h" #include "Hash.h" namespace nv { class Stream; /** Thatcher Ulrich's hash table. * * Hash table, linear probing, internal chaining. One * interesting/nice thing about this implementation is that the table * itself is a flat chunk of memory containing no pointers, only * relative indices. If the key and value types of the hash contain * no pointers, then the hash can be serialized using raw IO. Could * come in handy. * * Never shrinks, unless you explicitly clear() it. Expands on * demand, though. For best results, if you know roughly how big your * table will be, default it to that size when you create it. */ template, typename E = Equal > class NVCORE_CLASS HashMap { NV_FORBID_COPY(HashMap); public: /// Default ctor. HashMap() : entry_count(0), size_mask(-1), table(NULL) { } /// Ctor with size hint. explicit HashMap(int size_hint) : entry_count(0), size_mask(-1), table(NULL) { setCapacity(size_hint); } /// Dtor. ~HashMap() { clear(); } void set(const T& key, const U& value); void add(const T& key, const U& value); bool remove(const T& key); void clear(); bool isEmpty() const; bool get(const T& key, U* value = NULL, T* other_key = NULL) const; bool contains(const T & key) const; int size() const; int count() const; int capacity() const; void checkExpand(); void resize(int n); void setCapacity(int new_size); // Behaves much like std::pair. struct Entry { int next_in_chain; // internal chaining for collisions uint hash_value; // avoids recomputing. Worthwhile? T key; U value; Entry() : next_in_chain(-2) {} Entry(const Entry& e) : next_in_chain(e.next_in_chain), hash_value(e.hash_value), key(e.key), value(e.value) {} Entry(const T& k, const U& v, int next, int hash) : next_in_chain(next), hash_value(hash), key(k), value(v) {} bool isEmpty() const { return next_in_chain == -2; } bool isEndOfChain() const { return next_in_chain == -1; } bool isTombstone() const { return hash_value == TOMBSTONE_HASH; } void clear() { key.~T(); // placement delete value.~U(); // placement delete next_in_chain = -2; hash_value = ~TOMBSTONE_HASH; } void makeTombstone() { key.~T(); value.~U(); hash_value = TOMBSTONE_HASH; } }; // HashMap enumerator. typedef int PseudoIndex; PseudoIndex start() const { PseudoIndex i = 0; findNext(i); return i; } bool isDone(const PseudoIndex & i) const { nvDebugCheck(i <= size_mask+1); return i == size_mask+1; }; void advance(PseudoIndex & i) const { nvDebugCheck(i <= size_mask+1); i++; findNext(i); } #if NV_NEED_PSEUDOINDEX_WRAPPER Entry & operator[]( const PseudoIndexWrapper & i ) { Entry & e = entry(i(this)); nvDebugCheck(e.isTombstone() == false); return e; } const Entry & operator[]( const PseudoIndexWrapper & i ) const { const Entry & e = entry(i(this)); nvDebugCheck(e.isTombstone() == false); return e; } #else Entry & operator[](const PseudoIndex & i) { Entry & e = entry(i); nvDebugCheck(e.isTombstone() == false); return e; } const Entry & operator[](const PseudoIndex & i) const { const Entry & e = entry(i); nvDebugCheck(e.isTombstone() == false); return e; } #endif // By default we serialize the key-value pairs compactl y. template friend Stream & operator<< (Stream & s, HashMap<_T, _U, _H, _E> & map); // This requires more storage, but saves us from rehashing the elements. template friend Stream & rawSerialize(Stream & s, HashMap<_T, _U, _H, _E> & map); /// Swap the members of this vector and the given vector. template friend void swap(HashMap<_T, _U, _H, _E> & a, HashMap<_T, _U, _H, _E> & b); private: static const uint TOMBSTONE_HASH = (uint) -1; uint compute_hash(const T& key) const; // Find the index of the matching entry. If no match, then return -1. int findIndex(const T& key) const; // Return the index of the newly cleared element. int removeTombstone(int index); // Helpers. Entry & entry(int index); const Entry & entry(int index) const; void setRawCapacity(int new_size); // Move the enumerator to the next valid element. void findNext(PseudoIndex & i) const; int entry_count; int size_mask; Entry * table; }; } // nv namespace #endif // NV_CORE_HASHMAP_H