410 lines
10 KiB
C++
410 lines
10 KiB
C++
/*
|
|
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 <cstddef>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <memory>
|
|
#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<typename Key, typename Value>
|
|
struct KeyValue
|
|
{
|
|
const Key key;
|
|
Value value;
|
|
|
|
KeyValue( const Key& key_, const Value& value_ )
|
|
: key( key_ ), value( value_ ){
|
|
}
|
|
};
|
|
|
|
template<typename Key, typename Value, typename Hash>
|
|
struct BucketNode : public BucketNodeBase
|
|
{
|
|
Hash m_hash;
|
|
KeyValue<Key, Value> m_value;
|
|
|
|
BucketNode( Hash hash, const Key& key, const Value& value )
|
|
: m_hash( hash ), m_value( key, value ){
|
|
}
|
|
BucketNode* getNext() const {
|
|
return static_cast<BucketNode*>( next );
|
|
}
|
|
BucketNode* getPrev() const {
|
|
return static_cast<BucketNode*>( prev );
|
|
}
|
|
};
|
|
|
|
template<typename Key, typename Value, typename Hash>
|
|
class BucketIterator
|
|
{
|
|
typedef BucketNode<Key, Value, Hash> 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<Key, Value> 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<typename Key, typename Value, typename Hasher, typename KeyEqual = std::equal_to<Key> >
|
|
class HashTable : private KeyEqual, private Hasher
|
|
{
|
|
typedef typename Hasher::hash_type hash_type;
|
|
typedef HashTableDetail::KeyValue<Key, Value> KeyValue;
|
|
typedef HashTableDetail::BucketNode<Key, Value, hash_type> 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<BucketNode*>( m_list.next );
|
|
}
|
|
BucketNode* getLast(){
|
|
return static_cast<BucketNode*>( &m_list );
|
|
}
|
|
|
|
public:
|
|
|
|
typedef KeyValue value_type;
|
|
typedef HashTableDetail::BucketIterator<Key, Value, hash_type> 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
|