Improve preconditions for the container.Set

This commit is contained in:
Eugen Wissner 2018-05-11 05:43:14 +02:00
parent 41a8e32351
commit 53620cdddf
2 changed files with 77 additions and 74 deletions

View File

@ -46,44 +46,46 @@ package enum BucketStatus : byte
package struct Bucket(K, V = void) package struct Bucket(K, V = void)
{ {
private alias Key = K; static if (is(V == void))
private alias Value = V; {
K key_;
}
else
{
alias KV = Pair!(K, "key", V, "value");
KV kv;
}
BucketStatus status = BucketStatus.empty;
this(ref K key)
{
this.key = key;
}
@property void key(ref K key) @property void key(ref K key)
{ {
this.key_ = key; this.key() = key;
this.status = BucketStatus.used; this.status = BucketStatus.used;
} }
@property ref inout(K) key() inout @property ref inout(K) key() inout
{
static if (is(V == void))
{ {
return this.key_; return this.key_;
} }
else
bool opEquals(ref K key)
{ {
if (this.status == BucketStatus.used && this.key == key) return this.kv.key;
{
return true;
} }
return false;
} }
bool opEquals(ref const K key) const bool opEquals(ref inout(K) key) inout
{ {
if (this.status == BucketStatus.used && this.key == key) return this.status == BucketStatus.used && this.key == key;
{
return true;
}
return false;
} }
bool opEquals(ref typeof(this) that) bool opEquals(ref inout(typeof(this)) that) inout
{
return key == that.key && this.status == that.status;
}
bool opEquals(ref typeof(this) that) const
{ {
return key == that.key && this.status == that.status; return key == that.key && this.status == that.status;
} }
@ -96,13 +98,6 @@ package struct Bucket(K, V = void)
} }
this.status = BucketStatus.deleted; this.status = BucketStatus.deleted;
} }
private K key_;
static if (!is(V == void))
{
V value;
}
BucketStatus status = BucketStatus.empty;
} }
// Possible sizes for the hash-based containers. // Possible sizes for the hash-based containers.
@ -115,10 +110,12 @@ package static immutable size_t[33] primes = [
package struct HashArray(alias hasher, K, V = void) package struct HashArray(alias hasher, K, V = void)
{ {
alias Bucket = .Bucket!(K, V); alias Key = K;
alias Value = V;
alias Bucket = .Bucket!(Key, Value);
alias Buckets = Array!Bucket; alias Buckets = Array!Bucket;
Array!Bucket array; Buckets array;
size_t lengthIndex; size_t lengthIndex;
size_t length; size_t length;
@ -126,18 +123,16 @@ package struct HashArray(alias hasher, K, V = void)
* Returns bucket position for `hash`. `0` may mean the 0th position or an * Returns bucket position for `hash`. `0` may mean the 0th position or an
* empty `buckets` array. * empty `buckets` array.
*/ */
size_t locateBucket(ref const K key) const size_t locateBucket(ref const Key key) const
{ {
return this.array.length == 0 ? 0 : hasher(key) % this.array.length; return this.array.length == 0 ? 0 : hasher(key) % this.array.length;
} }
/* /*
* Inserts the value in an empty or deleted bucket. If the value is * Inserts a key into an empty or deleted bucket. If the key is
* already in there, does nothing and returns InsertStatus.found. If the * already in there, does nothing. Returns the bucket with the key.
* hash array is full returns InsertStatus.failed. Otherwise,
* InsertStatus.added is returned.
*/ */
ref Bucket insert(ref K key) ref Bucket insert(ref Key key)
{ {
while (true) while (true)
{ {
@ -170,6 +165,11 @@ package struct HashArray(alias hasher, K, V = void)
// Takes an index in the primes array. // Takes an index in the primes array.
bool rehashToSize(const size_t n) bool rehashToSize(const size_t n)
in
{
assert(n < primes.length);
}
do
{ {
auto storage = typeof(this.array)(primes[n], this.array.allocator); auto storage = typeof(this.array)(primes[n], this.array.allocator);
DataLoop: foreach (ref e1; this.array[]) DataLoop: foreach (ref e1; this.array[])
@ -180,7 +180,7 @@ package struct HashArray(alias hasher, K, V = void)
foreach (ref e2; storage[bucketPosition .. $]) foreach (ref e2; storage[bucketPosition .. $])
{ {
if (e2.status != BucketStatus.used) // Insert the value. if (e2.status != BucketStatus.used) // Insert the key
{ {
e2 = e1; e2 = e1;
continue DataLoop; continue DataLoop;
@ -220,12 +220,12 @@ package struct HashArray(alias hasher, K, V = void)
this.length = 0; this.length = 0;
} }
size_t remove(ref K value) size_t remove(ref Key key)
{ {
auto bucketPosition = locateBucket(value); auto bucketPosition = locateBucket(key);
foreach (ref e; this.array[bucketPosition .. $]) foreach (ref e; this.array[bucketPosition .. $])
{ {
if (e == value) // Found. if (e == key) // Found.
{ {
e.remove(); e.remove();
--this.length; --this.length;
@ -239,12 +239,12 @@ package struct HashArray(alias hasher, K, V = void)
return 0; return 0;
} }
bool find(ref const K value) const bool canFind(ref const Key key) const
{ {
auto bucketPosition = locateBucket(value); auto bucketPosition = locateBucket(key);
foreach (ref e; this.array[bucketPosition .. $]) foreach (ref e; this.array[bucketPosition .. $])
{ {
if (e == value) // Found. if (e == key) // Found.
{ {
return true; return true;
} }

View File

@ -27,17 +27,18 @@ import tanya.meta.transform;
* Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values. * Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values.
* *
* Params: * Params:
* E = Element type. * T = Type of the internal hash storage.
*/ */
struct Range(E) struct Range(T)
{ {
static if (isMutable!E) private alias E = CopyConstness!(T, T.Key);
static if (isMutable!T)
{ {
private alias DataRange = Array!(Bucket!(Unqual!E)).Range; private alias DataRange = T.array.Range;
} }
else else
{ {
private alias DataRange = Array!(Bucket!(Unqual!E)).ConstRange; private alias DataRange = T.array.ConstRange;
} }
private DataRange dataRange; private DataRange dataRange;
@ -69,63 +70,61 @@ struct Range(E)
@property void popFront() @property void popFront()
in in
{ {
assert(!this.dataRange.empty); assert(!empty);
assert(this.dataRange.front.status == BucketStatus.used); assert(this.dataRange.front.status == BucketStatus.used);
} }
out out
{ {
assert(this.dataRange.empty assert(empty || this.dataRange.back.status == BucketStatus.used);
|| this.dataRange.back.status == BucketStatus.used);
} }
do do
{ {
do do
{ {
dataRange.popFront(); this.dataRange.popFront();
} }
while (!dataRange.empty && dataRange.front.status != BucketStatus.used); while (!empty && dataRange.front.status != BucketStatus.used);
} }
@property void popBack() @property void popBack()
in in
{ {
assert(!this.dataRange.empty); assert(!empty);
assert(this.dataRange.back.status == BucketStatus.used); assert(this.dataRange.back.status == BucketStatus.used);
} }
out out
{ {
assert(this.dataRange.empty assert(empty || this.dataRange.back.status == BucketStatus.used);
|| this.dataRange.back.status == BucketStatus.used);
} }
do do
{ {
do do
{ {
dataRange.popBack(); this.dataRange.popBack();
} }
while (!dataRange.empty && dataRange.back.status != BucketStatus.used); while (!empty && dataRange.back.status != BucketStatus.used);
} }
@property ref inout(E) front() inout @property ref inout(E) front() inout
in in
{ {
assert(!this.dataRange.empty); assert(!empty);
assert(this.dataRange.front.status == BucketStatus.used); assert(this.dataRange.front.status == BucketStatus.used);
} }
do do
{ {
return dataRange.front.key; return this.dataRange.front.key;
} }
@property ref inout(E) back() inout @property ref inout(E) back() inout
in in
{ {
assert(!this.dataRange.empty); assert(!empty);
assert(this.dataRange.back.status == BucketStatus.used); assert(this.dataRange.back.status == BucketStatus.used);
} }
do do
{ {
return dataRange.back.key; return this.dataRange.back.key;
} }
Range opIndex() Range opIndex()
@ -133,7 +132,7 @@ struct Range(E)
return typeof(return)(this.dataRange[]); return typeof(return)(this.dataRange[]);
} }
Range!(const E) opIndex() const Range!(const T) opIndex() const
{ {
return typeof(return)(this.dataRange[]); return typeof(return)(this.dataRange[]);
} }
@ -146,7 +145,9 @@ struct Range(E)
* This $(D_PSYMBOL Set) is implemented using closed hashing. Hash collisions * This $(D_PSYMBOL Set) is implemented using closed hashing. Hash collisions
* are resolved with linear probing. * are resolved with linear probing.
* *
* Currently works only with integral types. * $(D_PARAM T) should be hashable with $(D_PARAM hasher). $(D_PARAM hasher) is
* a callable that accepts an argument of type $(D_PARAM T) and returns a hash
* value for it ($(D_KEYWORD size_t)).
* *
* Params: * Params:
* T = Element type. * T = Element type.
@ -155,15 +156,16 @@ struct Range(E)
struct Set(T, alias hasher = hash) struct Set(T, alias hasher = hash)
if (is(typeof(hasher(T.init)) == size_t)) if (is(typeof(hasher(T.init)) == size_t))
{ {
private HashArray!(hasher, T) data; private alias HashArray = .HashArray!(hasher, T);
private alias Buckets = HashArray.Buckets;
private alias Buckets = typeof(this.data).Buckets; private HashArray data;
/// The range types for $(D_PSYMBOL Set). /// The range types for $(D_PSYMBOL Set).
alias Range = .Range!T; alias Range = .Range!HashArray;
/// ditto /// ditto
alias ConstRange = .Range!(const T); alias ConstRange = .Range!(const HashArray);
invariant invariant
{ {
@ -200,7 +202,7 @@ if (is(typeof(hasher(T.init)) == size_t))
} }
do do
{ {
this.data = typeof(this.data)(Buckets(allocator)); this.data = HashArray(Buckets(allocator));
} }
/** /**
@ -222,7 +224,7 @@ if (is(typeof(hasher(T.init)) == size_t))
} }
do do
{ {
this.data = typeof(this.data)(Buckets(init.data, allocator)); this.data = HashArray(Buckets(init.data, allocator));
} }
/// ditto /// ditto
@ -234,7 +236,7 @@ if (is(typeof(hasher(T.init)) == size_t))
} }
do do
{ {
this.data = typeof(this.data)(Buckets(move(init.data), allocator)); this.data = HashArray(Buckets(move(init.data), allocator));
this.lengthIndex = init.lengthIndex; this.lengthIndex = init.lengthIndex;
init.lengthIndex = 0; init.lengthIndex = 0;
} }
@ -443,7 +445,7 @@ if (is(typeof(hasher(T.init)) == size_t))
*/ */
bool opBinaryRight(string op : "in")(auto ref const T value) const bool opBinaryRight(string op : "in")(auto ref const T value) const
{ {
return this.data.find(value); return this.data.canFind(value);
} }
/// ///
@ -486,8 +488,9 @@ if (is(typeof(hasher(T.init)) == size_t))
} }
/** /**
* Returns: A bidirectional range that iterates over the $(D_PSYMBOL Set)'s * Returns a bidirectional range over the container.
* elements. *
* Returns: A bidirectional range that iterates over the container.
*/ */
Range opIndex() Range opIndex()
{ {