Implement lookups in the Set

This commit is contained in:
Eugen Wissner 2017-05-31 10:29:07 +02:00
parent 0f365758e1
commit ea33ca62c8

View File

@ -217,6 +217,9 @@ struct Set(T)
12582917, 25165843, 139022417, 282312799, 573292817, 1164186217, 12582917, 25165843, 139022417, 282312799, 573292817, 1164186217,
]; ];
/// The maximum number of buckets the container can have.
enum size_t maxBucketCount = primes[$ - 1];
static private size_t calculateHash(ref T value) static private size_t calculateHash(ref T value)
{ {
static if (isIntegral!T || isSomeChar!T || is(T == bool)) static if (isIntegral!T || isSomeChar!T || is(T == bool))
@ -229,7 +232,7 @@ struct Set(T)
} }
} }
static private size_t locateBucket(ref DataType buckets, size_t hash) static private size_t locateBucket(ref const DataType buckets, size_t hash)
{ {
return hash % buckets.length; return hash % buckets.length;
} }
@ -241,15 +244,16 @@ struct Set(T)
added = 1, added = 1,
} }
// Inserts the value in an empty or deleted bucket. If the value is /*
// already in there, does nothing and returns true. If the hash array * Inserts the value in an empty or deleted bucket. If the value is
// is full returns false. * already in there, does nothing and returns true. If the hash array
static private InsertStatus insertInUnusedBucket(ref DataType buckets, * is full returns false.
ref T value) */
private InsertStatus insertInUnusedBucket(ref T value)
{ {
auto bucketPosition = locateBucket(buckets, calculateHash(value)); auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; buckets[bucketPosition .. $]) foreach (ref e; this.data[bucketPosition .. $])
{ {
if (e.content == value) // Already in the set. if (e.content == value) // Already in the set.
{ {
@ -281,10 +285,15 @@ struct Set(T)
this.data = DataType(primes[0], allocator); this.data = DataType(primes[0], allocator);
} }
InsertStatus status = insertInUnusedBucket(this.data, value); InsertStatus status = insertInUnusedBucket(value);
for (; !status; status = insertInUnusedBucket(this.data, value)) for (; !status; status = insertInUnusedBucket(value))
{ {
rehash(); if ((this.primes.length - 1) == this.lengthIndex)
{
throw make!HashContainerFullException(defaultAllocator,
"Set is full");
}
rehashToSize(this.lengthIndex + 1);
} }
return status == InsertStatus.added; return status == InsertStatus.added;
} }
@ -295,7 +304,8 @@ struct Set(T)
* Params: * Params:
* value = Element value. * value = Element value.
* *
* Returns: Amount of the elements removed. * Returns: Number of elements removed, which is in the container with
* unique values `1` if an element existed, and `0` otherwise.
*/ */
size_t remove(T value) size_t remove(T value)
{ {
@ -312,53 +322,160 @@ struct Set(T)
e.remove(); e.remove();
return 1; return 1;
} }
else if (e.status == BucketStatus.empty) // Insert the value. else if (e.status == BucketStatus.empty)
{ {
return 0; break;
} }
} }
return 0; return 0;
} }
private void rehash() /**
* $(D_KEYWORD in) operator.
*
* Params:
* value = Element to be searched for.
*
* Returns: $(D_KEYWORD true) if the given element exists in the container,
* $(D_KEYWORD false) otherwise.
*/
bool opBinaryRight(string op : "in")(auto ref T value)
{ {
if ((this.primes.length - 1) == this.lengthIndex) if (this.data.length == 0)
{ {
throw make!HashContainerFullException(defaultAllocator, return 0;
"Set is full");
} }
auto storage = DataType(primes[this.lengthIndex + 1], allocator); auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[]) foreach (ref e; this.data[bucketPosition .. $])
{ {
if (e.status == BucketStatus.used) if (e.content == value) // Found.
{ {
insertInUnusedBucket(storage, e.content); return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
/// Ditto.
bool opBinaryRight(string op : "in")(auto ref const T value) const
{
if (this.data.length == 0)
{
return 0;
}
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e.content == value) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
///
unittest
{
Set!int set;
assert(5 !in set);
set.insert(5);
assert(5 in set);
}
/**
* Sets the number of buckets in the container to at least $(D_PARAM n)
* and rearranges all the elements according to their hash values.
*
* If $(D_PARAM n) is greater than the current $(D_PSYMBOL capacity)
* and lower than or equal to $(D_PSYMBOL maxBucketCount), a rehash is
* forced.
*
* If $(D_PARAM n) is greater than $(D_PSYMBOL maxBucketCount),
* $(D_PSYMBOL maxBucketCount) is used instead as a new number of buckets.
i *
* If $(D_PARAM n) is equal to the current $(D_PSYMBOL capacity), rehashing
* is forced without resizing the container.
*
* If $(D_PARAM n) is lower than the current $(D_PSYMBOL capacity), the
* function may have no effect.
*
* Rehashing is automatically performed whenever the container needs space
* to insert new elements.
*
* Params:
* n = Minimum number of buckets.
*/
void rehash(const size_t n)
{
size_t lengthIndex;
for (; lengthIndex < primes.length; ++lengthIndex)
{
if (primes[lengthIndex] >= n)
{
break;
}
}
rehashToSize(lengthIndex);
}
// Takes an index in the primes array.
private void rehashToSize(const size_t n)
{
auto storage = DataType(primes[n], allocator);
DataLoop: foreach (ref e1; this.data[])
{
if (e1.status == BucketStatus.used)
{
auto bucketPosition = locateBucket(storage,
calculateHash(e1.content));
foreach (ref e2; storage[bucketPosition .. $])
{
if (e2.status != BucketStatus.used) // Insert the value.
{
e2.content = e1.content;
continue DataLoop;
}
}
return; // Rehashing failed.
} }
} }
move(storage, this.data); move(storage, this.data);
++this.lengthIndex; this.lengthIndex = n;
} }
private alias DataType = Array!(Bucket!T);
private DataType data;
private size_t lengthIndex;
/** /**
* Returns: A bidirectional range that iterates over the $(D_PSYMBOL Set)'s * Returns: A bidirectional range that iterates over the $(D_PSYMBOL Set)'s
* elements. * elements.
*/ */
Range opIndex() Range opIndex()
{ {
return typeof(return)(data[]); return typeof(return)(this.data[]);
} }
/// Ditto. /// Ditto.
ConstRange opIndex() const ConstRange opIndex() const
{ {
return typeof(return)(data[]); return typeof(return)(this.data[]);
} }
private alias DataType = Array!(Bucket!T);
private DataType data;
private size_t lengthIndex;
mixin DefaultAllocator; mixin DefaultAllocator;
} }