blob: 5e91193db235d8a8c3a4970c36faebf65ae646ba [file] [log] [blame]
/**
* HashTab container for internal usage.
*
* Copyright: Copyright Martin Nowak 2013.
* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
* Authors: Martin Nowak
*/
module core.internal.container.hashtab;
import core.internal.container.array;
static import common = core.internal.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); }
do
{
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 *opBinaryRight!("in")(key);
}
void opIndexAssign(Value value, Key key)
{
*get(key) = value;
}
inout(Value)* opBinaryRight(string op)(const scope Key key) inout
if (op == "in")
{
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 = opBinaryRight!("in")(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(const scope 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);
}
do
{
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);
}
do
{
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);
}