diff --git a/src/hashtable.cpp b/src/hashtable.cpp index bea549fc398e20b07652cf6b7d9e3eda0eefbb14..55c47d839ebbc669ad76481f924f198869785e0d 100644 --- a/src/hashtable.cpp +++ b/src/hashtable.cpp @@ -6,8 +6,9 @@ #include "hashtable.h" -Hashtable::Hashtable(size_t m, float alpha, float s): - slot_count(m), max_loadfactor(alpha), growfactor(s), table(new Slot[m]) +template <typename T> +Hashtable<T>::Hashtable(size_t m, float alpha, float s): + slot_count(m), max_loadfactor(alpha), growfactor(s), table(new Slot<T>[m]) { if (!(0 < alpha && alpha < 1)) @@ -16,30 +17,27 @@ Hashtable::Hashtable(size_t m, float alpha, float s): throw std::invalid_argument("grow factor s must be greater 1"); } -float Hashtable::load_factor() const { - return slot_count == 0 ? 0 : static_cast<float>(element_count)/slot_count; -} - -bool Hashtable::insert(unsigned key) { +template <typename T> +bool Hashtable<T>::insert(T key) { // Grow the table if inserting would exceed the load factor. // If the key is already in the table this could grow the table unnecessarily once. if (slot_count == 0 || (element_count+1.0f)/slot_count > max_loadfactor) grow(); - unsigned hk = hash(key); + size_t hk = hash(key); for (size_t i=0; i<slot_count; i++) { - Slot &slot = table[probe(hk, i)]; + Slot<T> &slot = table[probe(hk, i)]; - if (slot.label == Slot::used && slot.key == key) { + if (slot.label == Slot<T>::used && slot.key == key) { // the key is already in the table return false; } - if (slot.label == Slot::vacant || slot.label == Slot::deleted) { + if (slot.label == Slot<T>::vacant || slot.label == Slot<T>::deleted) { // found an empty slot element_count++; slot.key = key; - slot.label = Slot::used; + slot.label = Slot<T>::used; return true; } } @@ -49,23 +47,24 @@ bool Hashtable::insert(unsigned key) { throw std::logic_error("will we ever reach here?"); } -bool Hashtable::remove(unsigned key) { +template <typename T> +bool Hashtable<T>::remove(T key) { if (slot_count == 0) return false; // table is empty - unsigned hk = hash(key); + size_t hk = hash(key); for (size_t i=0; i<slot_count; i++) { - Slot &slot = table[probe(hk, i)]; + Slot<T> &slot = table[probe(hk, i)]; - if (slot.label == Slot::vacant) { + if (slot.label == Slot<T>::vacant) { // found a vacant slot: key is not in table break; } - if (slot.label == Slot::used && slot.key == key) { + if (slot.label == Slot<T>::used && slot.key == key) { // found the key: mark slot deleted element_count--; - slot.label = Slot::deleted; + slot.label = Slot<T>::deleted; return true; } } @@ -73,20 +72,21 @@ bool Hashtable::remove(unsigned key) { return false; } -bool Hashtable::contains(unsigned key) { +template <typename T> +bool Hashtable<T>::contains(T key) { if (slot_count == 0) return false; // table is empty - unsigned hk = hash(key); + size_t hk = hash(key); for (size_t i=0; i<slot_count; i++) { - Slot &slot = table[probe(hk, i)]; + Slot<T> &slot = table[probe(hk, i)]; - if (slot.label == Slot::vacant) { + if (slot.label == Slot<T>::vacant) { // found a vacant slot, key is not in table break; } - if (slot.label == Slot::used && slot.key == key) { + if (slot.label == Slot<T>::used && slot.key == key) { return true; } } @@ -94,36 +94,44 @@ bool Hashtable::contains(unsigned key) { return false; } -inline unsigned Hashtable::hash(unsigned key) { +template <typename T> +float Hashtable<T>::load_factor() const { + return slot_count == 0 ? 0 : static_cast<float>(element_count)/slot_count; +} + +template <typename T> +inline size_t Hashtable<T>::hash(T key) { assert(slot_count != 0); return key % slot_count; } -inline size_t Hashtable::probe(unsigned h, size_t i) { +template <typename T> +inline size_t Hashtable<T>::probe(size_t h, size_t i) { assert(slot_count != 0); return (h+i) % slot_count; } -void Hashtable::grow() { +template <typename T> +void Hashtable<T>::grow() { size_t old_capacity = slot_count; slot_count = std::ceil(growfactor * (slot_count==0 ? 1 : slot_count)); - Slot *const new_table = new Slot[slot_count]; + Slot<T> *const new_table = new Slot<T>[slot_count]; // for all slots in the old table ... for (size_t i=0; i<old_capacity; i++) { - Slot &os = table[i]; + Slot<T> &os = table[i]; // that store a key ... - if (os.label == Slot::used) { + if (os.label == Slot<T>::used) { unsigned hk = hash(os.key); // find a slot in the new table for (size_t j=0; j<slot_count; j++) { - Slot &ns = new_table[probe(hk, j)]; + Slot<T> &ns = new_table[probe(hk, j)]; - if (ns.label == Slot::vacant) { + if (ns.label == Slot<T>::vacant) { ns.key = os.key; - ns.label = Slot::used; + ns.label = Slot<T>::used; break; } } @@ -133,21 +141,23 @@ void Hashtable::grow() { table.reset(new_table); // smart_ptr frees the old table } -void Hashtable::info() const { +template <typename T> +void Hashtable<T>::info() const { std::cerr << "size: " << size() << ", capacity: " << capacity() << ", alpha (current/max): " << load_factor() << "/" << max_loadfactor << std::endl; - std::cerr << "array size: " << slot_count*sizeof(Slot)/1024 << "kB, start: " + std::cerr << "array size: " << slot_count*sizeof(Slot<T>)/1024 << "kB, start: " << table.get() << ", end: " << table.get()+slot_count << std::endl; } -void Hashtable::print() const { +template <typename T> +void Hashtable<T>::print() const { for (size_t i=0; i<slot_count; i+=10) { std::cerr << i << ": "; for (size_t j=i; j<i+10 && j<slot_count; j++) { - Slot s = table[j]; + const Slot<T> &s = table[j]; std::string repr; - if (s.label == Slot::vacant) repr = "F"; - else if(s.label == Slot::deleted) repr = "D"; + if (s.label == Slot<T>::vacant) repr = "F"; + else if(s.label == Slot<T>::deleted) repr = "D"; else repr = std::to_string(s.key); std::cerr << repr << " "; } diff --git a/src/hashtable.h b/src/hashtable.h index 94918fe62fe8ba05e2c0e99a07e0351237516b5c..a61e90191699c9e8de1a28fddbde264d6cc4c24c 100644 --- a/src/hashtable.h +++ b/src/hashtable.h @@ -4,22 +4,25 @@ #include <memory> +template<typename T> class Hashtable; + /* a bucket of size 1 */ +template <typename T> class Slot { - unsigned key = 0; + T key; enum: char { vacant, deleted, used } label = vacant; - friend class Hashtable; + friend Hashtable<T>; }; - +template <typename T> class Hashtable { public: Hashtable(size_t m=8, float alpha=0.75, float s=2.0); - bool insert(unsigned key); - bool remove(unsigned key); - bool contains(unsigned key); + bool insert(T key); + bool remove(T key); + bool contains(T key); size_t size() const { return element_count; } size_t capacity() const { return slot_count; } float load_factor() const; @@ -31,11 +34,18 @@ class Hashtable { size_t slot_count; const float max_loadfactor; const float growfactor; - std::unique_ptr<Slot[]> table; + std::unique_ptr<Slot<T>[]> table; - unsigned hash(unsigned key); - size_t probe(unsigned key, size_t i); + size_t hash(T key); + size_t probe(size_t h, size_t i); void grow(); }; +// declare some template instantiations explicitly +// so they are included in the shared library +template class Hashtable<unsigned int>; +template class Hashtable<unsigned long>; +template class Hashtable<unsigned short>; +template class Hashtable<unsigned char>; + #endif