OpenTTD/src/misc/hashtable.hpp

249 lines
7.0 KiB
C++

/*
* This file is part of OpenTTD.
* OpenTTD 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, version 2.
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file hashtable.hpp Hash table support. */
#ifndef HASHTABLE_HPP
#define HASHTABLE_HPP
#include "../core/math_func.hpp"
template <class TItem>
struct HashTableSlot
{
typedef typename TItem::Key Key; // make Titem::Key a property of HashTable
TItem *first_item = nullptr;
/** hash table slot helper - clears the slot by simple forgetting its items */
inline void Clear()
{
this->first_item = nullptr;
}
/** hash table slot helper - linear search for item with given key through the given blob - const version */
inline const TItem *Find(const Key &key) const
{
for (const TItem *item = this->first_item; item != nullptr; item = item->GetHashNext()) {
if (item->GetKey() == key) {
/* we have found the item, return it */
return item;
}
}
return nullptr;
}
/** hash table slot helper - linear search for item with given key through the given blob - non-const version */
inline TItem *Find(const Key &key)
{
for (TItem *item = this->first_item; item != nullptr; item = item->GetHashNext()) {
if (item->GetKey() == key) {
/* we have found the item, return it */
return item;
}
}
return nullptr;
}
/** hash table slot helper - add new item to the slot */
inline void Attach(TItem &new_item)
{
assert(new_item.GetHashNext() == nullptr);
new_item.SetHashNext(this->first_item);
this->first_item = &new_item;
}
/** hash table slot helper - remove item from a slot */
inline bool Detach(TItem &item_to_remove)
{
if (this->first_item == &item_to_remove) {
this->first_item = item_to_remove.GetHashNext();
item_to_remove.SetHashNext(nullptr);
return true;
}
TItem *item = this->first_item;
for (;;) {
if (item == nullptr) {
return false;
}
TItem *next_item = item->GetHashNext();
if (next_item == &item_to_remove) break;
item = next_item;
}
item->SetHashNext(item_to_remove.GetHashNext());
item_to_remove.SetHashNext(nullptr);
return true;
}
/** hash table slot helper - remove and return item from a slot */
inline TItem *Detach(const Key &key)
{
/* do we have any items? */
if (this->first_item == nullptr) {
return nullptr;
}
/* is it our first item? */
if (this->first_item->GetKey() == key) {
TItem &ret_item = *this->first_item;
this->first_item = this->first_item->GetHashNext();
ret_item.SetHashNext(nullptr);
return &ret_item;
}
/* find it in the following items */
TItem *previous_item = this->first_item;
for (TItem *item = this->first_item->GetHashNext(); item != nullptr; previous_item = item, item = item->GetHashNext()) {
if (item->GetKey() == key) {
/* we have found the item, unlink and return it */
previous_item->SetHashNext(item->GetHashNext());
item->SetHashNext(nullptr);
return item;
}
}
return nullptr;
}
};
/**
* class HashTable<Titem, HASH_BITS> - simple hash table
* of pointers allocated elsewhere.
*
* Supports: Add/Find/Remove of Titems.
*
* Your Titem must meet some extra requirements to be HashTable
* compliant:
* - its constructor/destructor (if any) must be public
* - if the copying of item requires an extra resource management,
* you must define also copy constructor
* - must support nested type (struct, class or typedef) Titem::Key
* that defines the type of key class for that item
* - must support public method:
* const Key& GetKey() const; // return the item's key object
*
* In addition, the Titem::Key class must support:
* - public method that calculates key's hash:
* int CalcHash() const;
* - public 'equality' operator to compare the key with another one
* bool operator==(const Key &other) const;
*/
template <class Titem, int Thash_bits_>
class HashTable {
public:
typedef typename Titem::Key Tkey; // make Titem::Key a property of HashTable
static constexpr int HASH_BITS = Thash_bits_; // publish num of hash bits
static constexpr int CAPACITY = 1 << HASH_BITS; // and num of slots 2^bits
protected:
/**
* each slot contains pointer to the first item in the list,
* Titem contains pointer to the next item - GetHashNext(), SetHashNext()
*/
typedef HashTableSlot<Titem> Slot;
Slot slots[CAPACITY]; // here we store our data (array of blobs)
int number_of_items = 0; // item counter
/** static helper - return hash for the given key modulo number of slots */
static inline int CalcHash(const Tkey &key)
{
uint32_t hash = key.CalcHash();
hash -= (hash >> 17); // hash * 131071 / 131072
hash -= (hash >> 5); // * 31 / 32
hash &= (1 << HASH_BITS) - 1; // modulo slots
return hash;
}
/** static helper - return hash for the given item modulo number of slots */
static inline int CalcHash(const Titem &item)
{
return CalcHash(item.GetKey());
}
public:
/** item count */
inline int Count() const
{
return this->number_of_items;
}
/** simple clear - forget all items - used by CSegmentCostCacheT.Flush() */
inline void Clear()
{
for (int i = 0; i < CAPACITY; i++) this->slots[i].Clear();
this->number_of_items = 0;
}
/** const item search */
const Titem *Find(const Tkey &key) const
{
int hash = CalcHash(key);
const Slot &slot = this->slots[hash];
const Titem *item = slot.Find(key);
return item;
}
/** non-const item search */
Titem *Find(const Tkey &key)
{
int hash = CalcHash(key);
Slot &slot = this->slots[hash];
Titem *item = slot.Find(key);
return item;
}
/** non-const item search & optional removal (if found) */
Titem *TryPop(const Tkey &key)
{
int hash = CalcHash(key);
Slot &slot = this->slots[hash];
Titem *item = slot.Detach(key);
if (item != nullptr) {
this->number_of_items--;
}
return item;
}
/** non-const item search & removal */
Titem &Pop(const Tkey &key)
{
Titem *item = TryPop(key);
assert(item != nullptr);
return *item;
}
/** non-const item search & optional removal (if found) */
bool TryPop(Titem &item)
{
const Tkey &key = item.GetKey();
int hash = CalcHash(key);
Slot &slot = this->slots[hash];
bool ret = slot.Detach(item);
if (ret) {
this->number_of_items--;
}
return ret;
}
/** non-const item search & removal */
void Pop(Titem &item)
{
[[maybe_unused]] bool ret = TryPop(item);
assert(ret);
}
/** add one item - copy it from the given item */
void Push(Titem &new_item)
{
int hash = CalcHash(new_item);
Slot &slot = this->slots[hash];
assert(slot.Find(new_item.GetKey()) == nullptr);
slot.Attach(new_item);
this->number_of_items++;
}
};
#endif /* HASHTABLE_HPP */