Implement lookups in the Set

This commit is contained in:
Eugen Wissner 2017-05-31 10:29:07 +02:00
parent 0f365758e1
commit ea33ca62c8
1 changed files with 146 additions and 29 deletions

View File

@ -217,6 +217,9 @@ struct Set(T)
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 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;
}
@ -241,15 +244,16 @@ struct Set(T)
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
// is full returns false.
static private InsertStatus insertInUnusedBucket(ref DataType buckets,
ref T value)
/*
* 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
* is full returns false.
*/
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.
{
@ -281,10 +285,15 @@ struct Set(T)
this.data = DataType(primes[0], allocator);
}
InsertStatus status = insertInUnusedBucket(this.data, value);
for (; !status; status = insertInUnusedBucket(this.data, value))
InsertStatus status = insertInUnusedBucket(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;
}
@ -295,7 +304,8 @@ struct Set(T)
* Params:
* 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)
{
@ -312,53 +322,160 @@ struct Set(T)
e.remove();
return 1;
}
else if (e.status == BucketStatus.empty) // Insert the value.
else if (e.status == BucketStatus.empty)
{
return 0;
break;
}
}
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,
"Set is full");
return 0;
}
auto storage = DataType(primes[this.lengthIndex + 1], allocator);
foreach (ref e; this.data[])
auto bucketPosition = locateBucket(this.data, calculateHash(value));
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);
++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
* elements.
*/
Range opIndex()
{
return typeof(return)(data[]);
return typeof(return)(this.data[]);
}
/// Ditto.
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;
}