/** * HashTab container for internal usage. * * Copyright: Copyright Martin Nowak 2013. * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Martin Nowak */ module rt.util.container.hashtab; import rt.util.container.array; static import common = rt.util.container.common; struct HashTab(Key, Value) { static struct Node { Key _key; Value _value; Node* _next; } @disable this(this); ~this() { reset(); } void reset() { foreach (p; _buckets) { while (p !is null) { auto pn = p._next; common.destroy(*p); common.free(p); p = pn; } } _buckets.reset(); _length = 0; } @property size_t length() const { return _length; } @property bool empty() const { return !_length; } void remove(in Key key) in { assert(key in this); } body { ensureNotInOpApply(); immutable hash = hashOf(key) & mask; auto pp = &_buckets[hash]; while (*pp) { auto p = *pp; if (p._key == key) { *pp = p._next; common.destroy(*p); common.free(p); if (--_length < _buckets.length && _length >= 4) shrink(); return; } else { pp = &p._next; } } assert(0); } ref inout(Value) opIndex(Key key) inout { return *opIn_r(key); } void opIndexAssign(Value value, Key key) { *get(key) = value; } inout(Value)* opIn_r(in Key key) inout { if (_buckets.length) { immutable hash = hashOf(key) & mask; for (inout(Node)* p = _buckets[hash]; p !is null; p = p._next) { if (p._key == key) return &p._value; } } return null; } int opApply(scope int delegate(ref Key, ref Value) dg) { immutable save = _inOpApply; _inOpApply = true; scope (exit) _inOpApply = save; foreach (p; _buckets) { while (p !is null) { if (auto res = dg(p._key, p._value)) return res; p = p._next; } } return 0; } private: Value* get(Key key) { if (auto p = opIn_r(key)) return p; ensureNotInOpApply(); if (!_buckets.length) _buckets.length = 4; immutable hash = hashOf(key) & mask; auto p = cast(Node*)common.xmalloc(Node.sizeof); common.initialize(*p); p._key = key; p._next = _buckets[hash]; _buckets[hash] = p; if (++_length >= 2 * _buckets.length) grow(); return &p._value; } static hash_t hashOf(in ref Key key) @trusted { static if (is(Key U : U[])) return .hashOf(key, 0); else return .hashOf((&key)[0 .. 1], 0); } @property hash_t mask() const { return _buckets.length - 1; } void grow() in { assert(_buckets.length); } body { immutable ocnt = _buckets.length; immutable nmask = 2 * ocnt - 1; _buckets.length = 2 * ocnt; for (size_t i = 0; i < ocnt; ++i) { auto pp = &_buckets[i]; while (*pp) { auto p = *pp; immutable nidx = hashOf(p._key) & nmask; if (nidx != i) { *pp = p._next; p._next = _buckets[nidx]; _buckets[nidx] = p; } else { pp = &p._next; } } } } void shrink() in { assert(_buckets.length >= 2); } body { immutable ocnt = _buckets.length; immutable ncnt = ocnt >> 1; immutable nmask = ncnt - 1; for (size_t i = ncnt; i < ocnt; ++i) { if (auto tail = _buckets[i]) { immutable nidx = i & nmask; auto pp = &_buckets[nidx]; while (*pp) pp = &(*pp)._next; *pp = tail; _buckets[i] = null; } } _buckets.length = ncnt; } void ensureNotInOpApply() { if (_inOpApply) assert(0, "Invalid HashTab manipulation during opApply iteration."); } Array!(Node*) _buckets; size_t _length; bool _inOpApply; } unittest { HashTab!(int, int) tab; foreach (i; 0 .. 100) tab[i] = 100 - i; foreach (i; 0 .. 100) assert(tab[i] == 100 - i); foreach (k, v; tab) assert(v == 100 - k); foreach (i; 0 .. 50) tab.remove(2 * i); assert(tab.length == 50); foreach (i; 0 .. 50) assert(tab[2 * i + 1] == 100 - 2 * i - 1); assert(tab.length == 50); tab.reset(); assert(tab.empty); tab[0] = 0; assert(!tab.empty); destroy(tab); assert(tab.empty); // not copyable static assert(!__traits(compiles, { HashTab!(int, int) tab2 = tab; })); HashTab!(int, int) tab2; static assert(!__traits(compiles, tab = tab2)); static void foo(HashTab!(int, int) copy) {} static assert(!__traits(compiles, foo(tab))); } unittest { HashTab!(string, size_t) tab; tab["foo"] = 0; assert(tab["foo"] == 0); ++tab["foo"]; assert(tab["foo"] == 1); tab["foo"]++; assert(tab["foo"] == 2); auto s = "fo"; s ~= "o"; assert(tab[s] == 2); assert(tab.length == 1); tab[s] -= 2; assert(tab[s] == 0); tab["foo"] = 12; assert(tab[s] == 12); tab.remove("foo"); assert(tab.empty); } unittest { alias RC = common.RC!(); HashTab!(size_t, RC) tab; size_t cnt; assert(cnt == 0); tab[0] = RC(&cnt); assert(cnt == 1); tab[1] = tab[0]; assert(cnt == 2); tab.remove(0); assert(cnt == 1); tab.remove(1); assert(cnt == 0); } unittest { import core.exception; HashTab!(uint, uint) tab; foreach (i; 0 .. 5) tab[i] = i; bool thrown; foreach (k, v; tab) { try { if (k == 3) tab.remove(k); } catch (AssertError e) { thrown = true; } } assert(thrown); assert(tab[3] == 3); }