diff --git a/octa/internal/hashtable.h b/octa/internal/hashtable.h new file mode 100644 index 0000000..5f3e6cf --- /dev/null +++ b/octa/internal/hashtable.h @@ -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 + +#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; + using CHA = octa::AllocatorRebind; + + using AllocPair = octa::detail::CompressedPair; + using FuncPair = octa::detail::CompressedPair; + using FAPair = octa::detail::CompressedPair; + using DataPair = octa::detail::CompressedPair; + + 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 + 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 + 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 + 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 + T *access(const U &key) const { + octa::Size h; + return access_base(key, h); + } + + template + 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 \ No newline at end of file diff --git a/octa/map.h b/octa/map.h new file mode 100644 index 0000000..6461d90 --- /dev/null +++ b/octa/map.h @@ -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 struct MapBase { + typedef octa::Pair Element; + + static inline const K &get_key(Element &e) { + return e.first; + } + static inline T &get_data(Element &e) { + return e.second; + } + template + 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, + typename C = octa::Equal, + typename A = octa::Allocator> +> struct Map { +private: + using Base = octa::detail::Hashtable< + octa::detail::MapBase, octa::Pair, 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; + using Reference = Value &; + using Pointer = octa::AllocatorPointer; + using ConstPointer = octa::AllocatorConstPointer; + 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 \ No newline at end of file