forked from OctaForge/libostd
initial hashtable/map implementation
This commit is contained in:
parent
850d20093a
commit
48b8ba7522
172
octa/internal/hashtable.h
Normal file
172
octa/internal/hashtable.h
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
/* Internal hash table implementation. Used as a base for set, map etc.
|
||||||
|
*
|
||||||
|
* This file is part of OctaSTD. See COPYING.md for futher information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef OCTA_INTERNAL_HASHTABLE_H
|
||||||
|
#define OCTA_INTERNAL_HASHTABLE_H
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "octa/types.h"
|
||||||
|
#include "octa/utility.h"
|
||||||
|
#include "octa/memory.h"
|
||||||
|
|
||||||
|
namespace octa {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template<
|
||||||
|
typename B, /* contains methods specific to each ht type */
|
||||||
|
typename E, /* element type */
|
||||||
|
typename K, /* key type */
|
||||||
|
typename T, /* value type */
|
||||||
|
typename H, /* hash func */
|
||||||
|
typename C, /* equality check */
|
||||||
|
typename A /* allocator */
|
||||||
|
> struct Hashtable {
|
||||||
|
static constexpr octa::Size CHUNKSIZE = 64;
|
||||||
|
|
||||||
|
struct Chain {
|
||||||
|
E value;
|
||||||
|
Chain *next;
|
||||||
|
};
|
||||||
|
struct Chunk {
|
||||||
|
Chain chains[CHUNKSIZE];
|
||||||
|
Chunk *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
octa::Size p_size;
|
||||||
|
octa::Size p_len;
|
||||||
|
|
||||||
|
Chunk *p_chunks;
|
||||||
|
Chain *p_unused;
|
||||||
|
|
||||||
|
|
||||||
|
using CPA = octa::AllocatorRebind<A, Chain *>;
|
||||||
|
using CHA = octa::AllocatorRebind<A, Chunk>;
|
||||||
|
|
||||||
|
using AllocPair = octa::detail::CompressedPair<CPA, CHA>;
|
||||||
|
using FuncPair = octa::detail::CompressedPair<H, C>;
|
||||||
|
using FAPair = octa::detail::CompressedPair<AllocPair, FuncPair>;
|
||||||
|
using DataPair = octa::detail::CompressedPair<Chain **, FAPair>;
|
||||||
|
|
||||||
|
DataPair p_data;
|
||||||
|
|
||||||
|
const H &get_hash() const { return p_data.second().second().first(); }
|
||||||
|
const C &get_eq() const { return p_data.second().second().second(); }
|
||||||
|
|
||||||
|
CPA &get_cpalloc() { return p_data.second().first().first(); }
|
||||||
|
CHA &get_challoc() { return p_data.second().first().second(); }
|
||||||
|
|
||||||
|
Hashtable(octa::Size size, const H &hf, const C &eqf, const A &alloc):
|
||||||
|
p_size(size), p_len(0), p_chunks(nullptr), p_unused(nullptr),
|
||||||
|
p_data(nullptr, FAPair(AllocPair(alloc, alloc), FuncPair(hf, eqf))) {
|
||||||
|
p_data.first() = octa::allocator_allocate(get_cpalloc(), size);
|
||||||
|
memset(p_data.first(), 0, size * sizeof(Chain *));
|
||||||
|
}
|
||||||
|
|
||||||
|
~Hashtable() {
|
||||||
|
octa::allocator_deallocate(get_cpalloc(), p_data.first(), p_size);
|
||||||
|
delete_chunks();
|
||||||
|
}
|
||||||
|
|
||||||
|
Chain *insert(octa::Size h) {
|
||||||
|
if (!p_unused) {
|
||||||
|
Chunk *chunk = octa::allocator_allocate(get_challoc(), 1);
|
||||||
|
octa::allocator_construct(get_challoc(), chunk);
|
||||||
|
chunk->next = p_chunks;
|
||||||
|
p_chunks = chunk;
|
||||||
|
for (Size i = 0; i < (CHUNKSIZE - 1); ++i)
|
||||||
|
chunk->chains[i].next = &chunk->chains[i + 1];
|
||||||
|
chunk->chains[CHUNKSIZE - 1].next = p_unused;
|
||||||
|
p_unused = chunk->chains;
|
||||||
|
}
|
||||||
|
Chain *c = p_unused;
|
||||||
|
p_unused = p_unused->next;
|
||||||
|
c->next = p_data.first()[h];
|
||||||
|
p_data.first()[h] = c;
|
||||||
|
++p_len;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
T &insert(octa::Size h, const U &key) {
|
||||||
|
Chain *c = insert(h);
|
||||||
|
B::set_key(c->value, key);
|
||||||
|
return B::get_data(c->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
bool remove(const U &key) {
|
||||||
|
octa::Size h = get_hash()(key) & (p_size - 1);
|
||||||
|
Chain *c = p_data.first()[h];
|
||||||
|
Chain **p = &c;
|
||||||
|
while (c) {
|
||||||
|
if (get_eq()(key, B::get_key(c->value))) {
|
||||||
|
*p = c->next;
|
||||||
|
c->value.~E();
|
||||||
|
new (&c->value) E;
|
||||||
|
c->next = p_unused;
|
||||||
|
p_unused = c;
|
||||||
|
--p_len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
c = c->next;
|
||||||
|
p = &c;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void delete_chunks() {
|
||||||
|
for (Chunk *nc; p_chunks; p_chunks = nc) {
|
||||||
|
nc = p_chunks->next;
|
||||||
|
octa::allocator_destroy(get_challoc(), p_chunks);
|
||||||
|
octa::allocator_deallocate(get_challoc(), p_chunks, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
if (!p_len) return;
|
||||||
|
memset(p_data.first(), 0, p_size * sizeof(Chain *));
|
||||||
|
p_len = 0;
|
||||||
|
p_unused = nullptr;
|
||||||
|
delete_chunks();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
T *access_base(const U &key, octa::Size &h) const {
|
||||||
|
h = get_hash()(key) & (p_size - 1);
|
||||||
|
for (Chain *c = p_data.first()[h]; c; c = c->next) {
|
||||||
|
if (get_eq()(key, B::get_key(c->value)))
|
||||||
|
return &B::get_data(c->value);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
T *access(const U &key) const {
|
||||||
|
octa::Size h;
|
||||||
|
return access_base(key, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U, typename V>
|
||||||
|
T &access(const U &key, const V &val) {
|
||||||
|
octa::Size h;
|
||||||
|
T *found = access_base(key, h);
|
||||||
|
if (found) return *found;
|
||||||
|
return (insert(h, key) = val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(Hashtable &h) {
|
||||||
|
octa::swap(p_size, h.p_size);
|
||||||
|
octa::swap(p_len, h.p_len);
|
||||||
|
octa::swap(p_chunks, h.p_chunks);
|
||||||
|
octa::swap(p_unused, h.p_unused);
|
||||||
|
octa::swap(p_data, h.p_data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} /* namespace detail */
|
||||||
|
|
||||||
|
} /* namespace octa */
|
||||||
|
|
||||||
|
#endif
|
96
octa/map.h
Normal file
96
octa/map.h
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/* Associative array for OctaSTD. Implemented as a hash table.
|
||||||
|
*
|
||||||
|
* This file is part of OctaSTD. See COPYING.md for futher information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef OCTA_MAP_H
|
||||||
|
#define OCTA_MAP_H
|
||||||
|
|
||||||
|
#include "octa/types.h"
|
||||||
|
#include "octa/utility.h"
|
||||||
|
#include "octa/memory.h"
|
||||||
|
#include "octa/functional.h"
|
||||||
|
|
||||||
|
#include "octa/internal/hashtable.h"
|
||||||
|
|
||||||
|
namespace octa {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template<typename K, typename T> struct MapBase {
|
||||||
|
typedef octa::Pair<const K, T> Element;
|
||||||
|
|
||||||
|
static inline const K &get_key(Element &e) {
|
||||||
|
return e.first;
|
||||||
|
}
|
||||||
|
static inline T &get_data(Element &e) {
|
||||||
|
return e.second;
|
||||||
|
}
|
||||||
|
template<typename U>
|
||||||
|
static inline void set_key(Element &e, const U &key) {
|
||||||
|
e.first.~K();
|
||||||
|
new ((K *)&e.first) K(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename K, typename T,
|
||||||
|
typename H = octa::ToHash<K>,
|
||||||
|
typename C = octa::Equal<K>,
|
||||||
|
typename A = octa::Allocator<octa::Pair<const K, T>>
|
||||||
|
> struct Map {
|
||||||
|
private:
|
||||||
|
using Base = octa::detail::Hashtable<
|
||||||
|
octa::detail::MapBase<K, T>, octa::Pair<const K, T>, K, T, H, C, A
|
||||||
|
>;
|
||||||
|
Base p_table;
|
||||||
|
public:
|
||||||
|
|
||||||
|
using Key = K;
|
||||||
|
using Mapped = T;
|
||||||
|
using Size = octa::Size;
|
||||||
|
using Difference = octa::Ptrdiff;
|
||||||
|
using Hasher = H;
|
||||||
|
using KeyEqual = C;
|
||||||
|
using Value = octa::Pair<const K, T>;
|
||||||
|
using Reference = Value &;
|
||||||
|
using Pointer = octa::AllocatorPointer<A>;
|
||||||
|
using ConstPointer = octa::AllocatorConstPointer<A>;
|
||||||
|
using Allocator = A;
|
||||||
|
|
||||||
|
Map(octa::Size size = 1 << 10, const H &hf = H(), const C &eqf = C(),
|
||||||
|
const A &alloc = A()): p_table(size, hf, eqf, alloc) {}
|
||||||
|
|
||||||
|
bool empty() const { return p_table.p_len == 0; }
|
||||||
|
octa::Size size() const { return p_table.p_len; }
|
||||||
|
|
||||||
|
octa::Size bucket_count() const { return p_table.p_size; }
|
||||||
|
|
||||||
|
void clear() { p_table.clear(); }
|
||||||
|
|
||||||
|
A get_allocator() const {
|
||||||
|
return p_table.get_challoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
T &at(const K &key) {
|
||||||
|
return *p_table.access(key);
|
||||||
|
}
|
||||||
|
const T &at(const K &key) const {
|
||||||
|
return *p_table.access(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
T &operator[](const K &key) {
|
||||||
|
octa::Size h;
|
||||||
|
T *v = p_table.access_base(key, h);
|
||||||
|
if (v) return *v;
|
||||||
|
return p_table.insert(h, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(Map &v) {
|
||||||
|
octa::swap(p_table, v.p_table);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace detail */
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue