#ifndef LITB_HASHTABLE_H
#define LITB_HASHTABLE_H
#include "vector.hpp"
#include "utils.hpp"
#include <utility>
#include <cstdint>
namespace litb {
namespace detail {
uint32_t hash() = delete;
}
// A hash table mapping "Key" to "Value".
// "Key" must provide a "hash" function that is
// found by argument-dependent-lookup.
template<typename Key, typename Value>
class HashTable {
struct Bucket;
template<typename BucketType>
struct Iterator;
typedef Iterator<Bucket> iterator;
typedef Iterator<const Bucket> const_iterator;
typedef Pair<const Key, Value> value_type;
typedef Key key_type;
typedef Value mapped_type;
public:
HashTable():_size() {}
~Hash() {
clear();
}
// returns the size (number of keys) of this table
int size() const {
return _size;
}
// inserts the given mapping, returning false if the key was already mapped. in that case,
// the value will not be updated.
bool insert(const Pair<Key, Value>& mapping) {
struct C {
const Value &operator()() const {
return value;
}
const Value& value;
} creator = { mapping.second };
return internalInsert(key, creator);
}
// inserts a mapping of key to value.
// returns true if the mapping was inserted, and false if the key was already
// mapped. In that case, the value will not be updated.
bool insert(const Key& key, const Value& value) {
struct C {
const Value &operator()() const {
return value;
}
const Value& value;
} creator = { value };
return internalInsert(key, creator);
}
// returns true if the table contains a mapping from key to a value
bool contains(const Key& key) const {
return getBucket(key) != nullptr;
}
// returns a reference to the value mapped by the given key. If the element
// does not exist yet, it is value-initialized and copied to the map.
Value &at(const Key& key) {
struct C {
Value operator()() const {
return Value();
}
} creator;
return getOrCreateBucket(key, c)->_value;
}
Value &operator[](const Key& key) {
return at(key);
}
// if for this overload the element does not exist, behavior is undefined
const Value& at(const Key& key) const {
return getBucket(key)->_value;
}
const Value& operator[](const Key& key) const {
return at(key);
}
// after calling this the map contains no elements anymore
void clear() {
erase(begin(), end());
}
private:
// one bucket in the buckets array
struct Bucket {
Bucket(const Key& key, const Value& value, Bucket *next)
:_data(key, value), _next(next)
{ }
value_type _data;
Bucket *_next;
};
template<typename BucketType>
struct Iterator {
typedef HashTable::value_type value_type;
typedef typename TransferCv<value_type, BucketType>::type &reference_type;
typedef typename TransferCv<value_type, BucketType>::type *pointer;
typedef int difference_type;
typedef std::forward_iterator_tag iterator_category;
IteratorBase(BucketType *const *bucketHead)
:_bucketHead(bucketHead), _bucket(bucketHead)
{ }
public:
reference_type operator*() const {
return _bucket->_data;
}
pointer operator->() const {
return &_bucket->_data;
}
// convenience wrappers key() and value() return the key and the mapped data
// respectively
const key_type& key() const {
return (*this)->first;
}
typedef typename TransferCv<mapped_type, BucketType>::type &value() const {
return (*this)->second;
}
public:
Iterator operator++(int) {
Iterator it(*this);
++it;
return it;
}
Iterator &operator++() {
if(!_bucket->_next) {
_bucketHead++;
_bucket = _bucketHead;
}
return *this;
}
BucketType *const *_bucketHead;
BucketType *_bucket;
};
private:
static uint32_t hash(const Key& key) const {
using detail::hash;
return hash(key);
}
template<typename Creator>
bool internalInsert(const Key& key, const Creator& c) {
int oldSize = size();
Bucket *bucket = getOrCreateBucket(key, c);
return size() != oldSize;
}
// returns a bucket for the given key and value.
// if not yet existing, call "creator()" to create and return a new
// instance of the value.
template<typename Creator>
Bucket *getOrCreateBucket(const Key& key, const Creator& c) {
if(_buckets.empty()) {
_buckets.resize(128);
} else {
const float loadFactor = size() / (float)_buckets.size();
if(loadFactor > 0.75) {
rehash();
}
}
int bucketIndex = hash(key) % _buckets.size();
Bucket *bucket = _buckets[bucketIndex];
for(Bucket *bucket1 = bucket; bucket1; bucket1 = bucket1->_next) {
if(bucket1->_data.first == key) {
return bucket1;
}
}
bucket = new Bucket(key, c(), bucket);
_buckets[bucketIndex] = bucket;
_size++;
return bucket;
}
// returns the bucket for the given key and nullptr if it is not found
const Bucket *getBucket(const Key& key) const {
int bucketIndex = hash(key) % _buckets.size();
const Bucket *bucket = _buckets[bucketIndex];
for(const Bucket *bucket1 = bucket; bucket1; bucket1 = bucket1->_next) {
if(bucket1->_data.first == key) {
return bucket1;
}
}
return nullptr;
}
// this will reallocate the bucket array and rehash everything
void rehash() {
int newSize = _buckets.size() * 2;
VectorBase<Bucket*> newBuckets;
newBuckets.resize(newSize);
for(Bucket *bucket : _buckets) {
while(bucket) {
int bucketIndex = hash(bucket->_data.first) % newBuckets.size();
Bucket *bucket1 = bucket;
bucket = bucket->next;
bucket1->next = newBuckets[bucketIndex];
newBuckets[bucketIndex] = bucket1;
}
}
_buckets = std::move(newBuckets);
}
private:
VectorBase<Bucket*> _buckets;
int _size;
};
}
#endif