diff --git a/source/tanya/container/entry.d b/source/tanya/container/entry.d index a3cc9ee..6b6fd2a 100644 --- a/source/tanya/container/entry.d +++ b/source/tanya/container/entry.d @@ -46,44 +46,46 @@ package enum BucketStatus : byte package struct Bucket(K, V = void) { - private alias Key = K; - private alias Value = V; + static if (is(V == void)) + { + 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) { - this.key_ = key; + this.key() = key; this.status = BucketStatus.used; } @property ref inout(K) key() inout { - return this.key_; - } - - bool opEquals(ref K key) - { - if (this.status == BucketStatus.used && this.key == key) + static if (is(V == void)) { - return true; + return this.key_; } - return false; - } - - bool opEquals(ref const K key) const - { - if (this.status == BucketStatus.used && this.key == key) + else { - return true; + return this.kv.key; } - return false; } - bool opEquals(ref typeof(this) that) + bool opEquals(ref inout(K) key) inout { - return key == that.key && this.status == that.status; + return this.status == BucketStatus.used && this.key == key; } - bool opEquals(ref typeof(this) that) const + bool opEquals(ref inout(typeof(this)) that) inout { return key == that.key && this.status == that.status; } @@ -96,13 +98,6 @@ package struct Bucket(K, V = void) } 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. @@ -115,10 +110,12 @@ package static immutable size_t[33] primes = [ 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; - Array!Bucket array; + Buckets array; size_t lengthIndex; 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 * 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; } /* - * Inserts the value in an empty or deleted bucket. If the value is - * already in there, does nothing and returns InsertStatus.found. If the - * hash array is full returns InsertStatus.failed. Otherwise, - * InsertStatus.added is returned. + * Inserts a key into an empty or deleted bucket. If the key is + * already in there, does nothing. Returns the bucket with the key. */ - ref Bucket insert(ref K key) + ref Bucket insert(ref Key key) { while (true) { @@ -170,6 +165,11 @@ package struct HashArray(alias hasher, K, V = void) // Takes an index in the primes array. bool rehashToSize(const size_t n) + in + { + assert(n < primes.length); + } + do { auto storage = typeof(this.array)(primes[n], this.array.allocator); DataLoop: foreach (ref e1; this.array[]) @@ -180,7 +180,7 @@ package struct HashArray(alias hasher, K, V = void) foreach (ref e2; storage[bucketPosition .. $]) { - if (e2.status != BucketStatus.used) // Insert the value. + if (e2.status != BucketStatus.used) // Insert the key { e2 = e1; continue DataLoop; @@ -220,12 +220,12 @@ package struct HashArray(alias hasher, K, V = void) 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 .. $]) { - if (e == value) // Found. + if (e == key) // Found. { e.remove(); --this.length; @@ -239,12 +239,12 @@ package struct HashArray(alias hasher, K, V = void) 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 .. $]) { - if (e == value) // Found. + if (e == key) // Found. { return true; } diff --git a/source/tanya/container/set.d b/source/tanya/container/set.d index 6e158f1..207f485 100644 --- a/source/tanya/container/set.d +++ b/source/tanya/container/set.d @@ -27,17 +27,18 @@ import tanya.meta.transform; * Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values. * * 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 { - private alias DataRange = Array!(Bucket!(Unqual!E)).ConstRange; + private alias DataRange = T.array.ConstRange; } private DataRange dataRange; @@ -69,63 +70,61 @@ struct Range(E) @property void popFront() in { - assert(!this.dataRange.empty); + assert(!empty); assert(this.dataRange.front.status == BucketStatus.used); } out { - assert(this.dataRange.empty - || this.dataRange.back.status == BucketStatus.used); + assert(empty || this.dataRange.back.status == BucketStatus.used); } 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() in { - assert(!this.dataRange.empty); + assert(!empty); assert(this.dataRange.back.status == BucketStatus.used); } out { - assert(this.dataRange.empty - || this.dataRange.back.status == BucketStatus.used); + assert(empty || this.dataRange.back.status == BucketStatus.used); } 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 in { - assert(!this.dataRange.empty); + assert(!empty); assert(this.dataRange.front.status == BucketStatus.used); } do { - return dataRange.front.key; + return this.dataRange.front.key; } @property ref inout(E) back() inout in { - assert(!this.dataRange.empty); + assert(!empty); assert(this.dataRange.back.status == BucketStatus.used); } do { - return dataRange.back.key; + return this.dataRange.back.key; } Range opIndex() @@ -133,7 +132,7 @@ struct Range(E) return typeof(return)(this.dataRange[]); } - Range!(const E) opIndex() const + Range!(const T) opIndex() const { return typeof(return)(this.dataRange[]); } @@ -146,7 +145,9 @@ struct Range(E) * This $(D_PSYMBOL Set) is implemented using closed hashing. Hash collisions * 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: * T = Element type. @@ -155,15 +156,16 @@ struct Range(E) struct Set(T, alias hasher = hash) 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). - alias Range = .Range!T; + alias Range = .Range!HashArray; /// ditto - alias ConstRange = .Range!(const T); + alias ConstRange = .Range!(const HashArray); invariant { @@ -200,7 +202,7 @@ if (is(typeof(hasher(T.init)) == size_t)) } 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 { - this.data = typeof(this.data)(Buckets(init.data, allocator)); + this.data = HashArray(Buckets(init.data, allocator)); } /// ditto @@ -234,7 +236,7 @@ if (is(typeof(hasher(T.init)) == size_t)) } do { - this.data = typeof(this.data)(Buckets(move(init.data), allocator)); + this.data = HashArray(Buckets(move(init.data), allocator)); this.lengthIndex = init.lengthIndex; 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 { - 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 - * elements. + * Returns a bidirectional range over the container. + * + * Returns: A bidirectional range that iterates over the container. */ Range opIndex() {