/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined(INCLUDED_CONTAINER_HASHTABLE_H) #define INCLUDED_CONTAINER_HASHTABLE_H #include #include #include #include "debugging/debugging.h" namespace HashTableDetail { inline std::size_t next_power_of_two(std::size_t size) { std::size_t result = 1; while(result < size) { result <<= 1; } return result; } struct BucketNodeBase { BucketNodeBase* next; BucketNodeBase* prev; }; inline void list_initialise(BucketNodeBase& self) { self.next = self.prev = &self; } inline void list_swap(BucketNodeBase& self, BucketNodeBase& other) { BucketNodeBase tmp(self); if(other.next == &other) { list_initialise(self); } else { self = other; self.next->prev = self.prev->next = &self; } if(tmp.next == &self) { list_initialise(other); } else { other = tmp; other.next->prev = other.prev->next = &other; } } inline void node_link(BucketNodeBase* node, BucketNodeBase* next) { node->next = next; node->prev = next->prev; next->prev = node; node->prev->next = node; } inline void node_unlink(BucketNodeBase* node) { node->prev->next = node->next; node->next->prev = node->prev; } template struct KeyValue { const Key key; Value value; KeyValue(const Key& key_, const Value& value_) : key(key_), value(value_) { } }; template struct BucketNode : public BucketNodeBase { Hash m_hash; KeyValue m_value; BucketNode(Hash hash, const Key& key, const Value& value) : m_hash(hash), m_value(key, value) { } BucketNode* getNext() const { return static_cast(next); } BucketNode* getPrev() const { return static_cast(prev); } }; template class BucketIterator { typedef BucketNode Node; Node* m_node; void increment() { m_node = m_node->getNext(); } public: typedef std::forward_iterator_tag iterator_category; typedef std::ptrdiff_t difference_type; typedef difference_type distance_type; typedef KeyValue value_type; typedef value_type* pointer; typedef value_type& reference; BucketIterator(Node* node) : m_node(node) { } Node* node() { return m_node; } bool operator==(const BucketIterator& other) const { return m_node == other.m_node; } bool operator!=(const BucketIterator& other) const { return !operator==(other); } BucketIterator& operator++() { increment(); return *this; } BucketIterator operator++(int) { BucketIterator tmp = *this; increment(); return tmp; } value_type& operator*() const { return m_node->m_value; } value_type* operator->() const { return &(operator*()); } }; } /// A hash-table container which maps keys to values. /// /// - Inserting or removing elements does not invalidate iterators. /// - Inserting or retrieving an element for a given key takes O(1) time on average. /// - Elements are stored in no particular order. /// /// \param Key Uniquely identifies a value. Must provide a copy-constructor. /// \param Value The value to be stored . Must provide a default-constructor and a copy-constructor. /// \param Hasher Must provide 'std::size_t operator()(const Key&) const' which always returns the same result if the same argument is given. /// \param KeyEqual Must provide 'bool operator==(const Key&, const Key&) const' which returns true only if both arguments are equal. /// /// \dontinclude container/hashtable.cpp /// \skipline HashTable example /// \until end example template > class HashTable : private KeyEqual, private Hasher { typedef typename Hasher::hash_type hash_type; typedef HashTableDetail::KeyValue KeyValue; typedef HashTableDetail::BucketNode BucketNode; inline BucketNode* node_create(hash_type hash, const Key& key, const Value& value) { return new BucketNode(hash, key, value); } inline void node_destroy(BucketNode* node) { delete node; } typedef BucketNode* Bucket; static Bucket* buckets_new(std::size_t count) { Bucket* buckets = new Bucket[count]; std::uninitialized_fill(buckets, buckets + count, Bucket(0)); return buckets; } static void buckets_delete(Bucket* buckets) { delete[] buckets; } std::size_t m_bucketCount; Bucket* m_buckets; std::size_t m_size; HashTableDetail::BucketNodeBase m_list; BucketNode* getFirst() { return static_cast(m_list.next); } BucketNode* getLast() { return static_cast(&m_list); } public: typedef KeyValue value_type; typedef HashTableDetail::BucketIterator iterator; private: void initialise() { list_initialise(m_list); } hash_type hashKey(const Key& key) { return Hasher::operator()(key); } std::size_t getBucketId(hash_type hash) const { return hash & (m_bucketCount - 1); } Bucket& getBucket(hash_type hash) { return m_buckets[getBucketId(hash)]; } BucketNode* bucket_find(Bucket bucket, hash_type hash, const Key& key) { std::size_t bucketId = getBucketId(hash); for(iterator i(bucket); i != end(); ++i) { hash_type nodeHash = i.node()->m_hash; if(getBucketId(nodeHash) != bucketId) { return 0; } if(nodeHash == hash && KeyEqual::operator()((*i).key, key)) { return i.node(); } } return 0; } BucketNode* bucket_insert(Bucket& bucket, BucketNode* node) { // link node into list node_link(node, bucket_next(bucket)); bucket = node; return node; } BucketNode* bucket_next(Bucket& bucket) { Bucket* end = m_buckets + m_bucketCount; for(Bucket* i = &bucket; i != end; ++i) { if(*i != 0) { return *i; } } return getLast(); } void buckets_resize(std::size_t count) { BucketNode* first = getFirst(); BucketNode* last = getLast(); buckets_delete(m_buckets); m_bucketCount = count; m_buckets = buckets_new(m_bucketCount); initialise(); for(BucketNode* i = first; i != last;) { BucketNode* node = i; i = i->getNext(); bucket_insert(getBucket((*node).m_hash), node); } } void size_increment() { if(m_size == m_bucketCount) { buckets_resize(m_bucketCount == 0 ? 8 : m_bucketCount << 1); } ++m_size; } void size_decrement() { --m_size; } HashTable(const HashTable& other); HashTable& operator=(const HashTable& other); public: HashTable() : m_bucketCount(0), m_buckets(0), m_size(0) { initialise(); } HashTable(std::size_t bucketCount) : m_bucketCount(HashTableDetail::next_power_of_two(bucketCount)), m_buckets(buckets_new(m_bucketCount)), m_size(0) { initialise(); } ~HashTable() { for(BucketNode* i = getFirst(); i != getLast();) { BucketNode* node = i; i = i->getNext(); node_destroy(node); } buckets_delete(m_buckets); } iterator begin() { return iterator(getFirst()); } iterator end() { return iterator(getLast()); } bool empty() const { return m_size == 0; } std::size_t size() const { return m_size; } /// \brief Returns an iterator pointing to the value associated with \p key if it is contained by the hash-table, else \c end(). iterator find(const Key& key) { hash_type hash = hashKey(key); if(m_bucketCount != 0) { Bucket bucket = getBucket(hash); if(bucket != 0) { BucketNode* node = bucket_find(bucket, hash, key); if(node != 0) { return iterator(node); } } } return end(); } /// \brief Adds \p value to the hash-table associated with \p key if it does not exist. iterator insert(const Key& key, const Value& value) { hash_type hash = hashKey(key); if(m_bucketCount != 0) { Bucket& bucket = getBucket(hash); if(bucket != 0) { BucketNode* node = bucket_find(bucket, hash, key); if(node != 0) { return iterator(node); } } } size_increment(); return iterator(bucket_insert(getBucket(hash), node_create(hash, key, value))); } /// \brief Removes the value pointed to by \p i from the hash-table. /// /// \p i must be a deferenceable iterator into the hash-table. void erase(iterator i) { Bucket& bucket = getBucket(i.node()->m_hash); BucketNode* node = i.node(); // if this was the last node in the bucket if(bucket == node) { bucket = (node->getNext() == getLast() || &getBucket(node->getNext()->m_hash) != &bucket) ? 0 : node->getNext(); } node_unlink(node); ASSERT_MESSAGE(node != 0, "tried to erase a non-existent key/value"); node_destroy(node); size_decrement(); } /// \brief Returns the value identified by \p key if it is contained by the hash-table, else inserts and returns a new default-constructed value associated with \p key. Value& operator[](const Key& key) { hash_type hash = hashKey(key); if(m_bucketCount != 0) { Bucket& bucket = getBucket(hash); if(bucket != 0) { BucketNode* node = bucket_find(bucket, hash, key); if(node != 0) { return node->m_value.value; } } } size_increment(); return bucket_insert(getBucket(hash), node_create(hash, key, Value()))->m_value.value; } /// \brief Removes the value associated with \p key from the hash-table. void erase(const Key& key) { erase(find(key)); } /// \brief Swaps the contents of the hash-table with \p other. void swap(HashTable& other) { std::swap(m_buckets, other.m_buckets); std::swap(m_bucketCount, other.m_bucketCount); std::swap(m_size, other.m_size); HashTableDetail::list_swap(m_list, other.m_list); } /// \brief Removes all values from the hash-table. void clear() { HashTable tmp; tmp.swap(*this); } }; #endif