Make HashTable work complex types as key

- Add toHash() function for String
- The key type shouldn't match exact for a lookup.
The key type and lookup key type should be comparable.
- Move elements when inserting if passed by value.
This commit is contained in:
Eugen Wissner 2018-06-28 12:14:45 +02:00
parent 533fa3b023
commit 5e901f505c
4 changed files with 111 additions and 13 deletions

View File

@ -82,12 +82,18 @@ package struct Bucket(K, V = void)
} }
} }
bool opEquals(ref inout(K) key) inout void moveKey(ref K key)
{
move(key, this.key());
this.status = BucketStatus.used;
}
bool opEquals(T)(ref const T key) const
{ {
return this.status == BucketStatus.used && this.key == key; return this.status == BucketStatus.used && this.key == key;
} }
bool opEquals(ref inout(typeof(this)) that) inout bool opEquals(ref const(typeof(this)) that) const
{ {
return key == that.key && this.status == that.status; return key == that.key && this.status == that.status;
} }
@ -180,7 +186,7 @@ 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 Key key) const size_t locateBucket(T)(ref const T key) const
{ {
return this.array.length == 0 ? 0 : hasher(key) % bucketCount; return this.array.length == 0 ? 0 : hasher(key) % bucketCount;
} }
@ -304,7 +310,7 @@ package struct HashArray(alias hasher, K, V = void)
return 0; return 0;
} }
bool opBinaryRight(string op : "in")(ref inout(Key) key) inout bool opBinaryRight(string op : "in", T)(ref const T key) const
{ {
foreach (ref e; this.array[locateBucket(key) .. $]) foreach (ref e; this.array[locateBucket(key) .. $])
{ {

View File

@ -14,6 +14,7 @@
*/ */
module tanya.container.hashtable; module tanya.container.hashtable;
import tanya.algorithm.mutation;
import tanya.container.array; import tanya.container.array;
import tanya.container.entry; import tanya.container.entry;
import tanya.hash.lookup; import tanya.hash.lookup;
@ -155,7 +156,7 @@ struct Range(T)
* hasher = Hash function for $(D_PARAM Key). * hasher = Hash function for $(D_PARAM Key).
*/ */
struct HashTable(Key, Value, alias hasher = hash) struct HashTable(Key, Value, alias hasher = hash)
if (is(typeof(hasher(Key.init)) == size_t)) if (is(typeof(((Key k) => hasher(k))(Key.init)) == size_t))
{ {
private alias HashArray = .HashArray!(hasher, Key, Value); private alias HashArray = .HashArray!(hasher, Key, Value);
private alias Buckets = HashArray.Buckets; private alias Buckets = HashArray.Buckets;
@ -468,15 +469,29 @@ if (is(typeof(hasher(Key.init)) == size_t))
* *
* Returns: Just inserted element. * Returns: Just inserted element.
*/ */
ref Value opIndexAssign(Value value, Key key) ref Value opIndexAssign()(auto ref Value value, auto ref Key key)
{ {
auto e = ((ref v) @trusted => &this.data.insert(v))(key); auto e = ((ref v) @trusted => &this.data.insert(v))(key);
if (e.status != BucketStatus.used) if (e.status != BucketStatus.used)
{
static if (__traits(isRef, key))
{ {
e.key = key; e.key = key;
} }
else
{
e.moveKey(key);
}
}
static if (__traits(isRef, value))
{
return e.kv.value = value; return e.kv.value = value;
} }
else
{
return e.kv.value = move(value);
}
}
/// ///
@nogc nothrow pure @safe unittest @nogc nothrow pure @safe unittest
@ -505,7 +520,7 @@ if (is(typeof(hasher(Key.init)) == size_t))
* *
* Returns: The number of the inserted elements with a unique key. * Returns: The number of the inserted elements with a unique key.
*/ */
size_t insert(KeyValue keyValue) size_t insert(ref KeyValue keyValue)
{ {
auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key); auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key);
size_t inserted; size_t inserted;
@ -518,6 +533,20 @@ if (is(typeof(hasher(Key.init)) == size_t))
return inserted; return inserted;
} }
/// ditto
size_t insert(KeyValue keyValue)
{
auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key);
size_t inserted;
if (e.status != BucketStatus.used)
{
e.moveKey(keyValue.key);
inserted = 1;
}
move(keyValue.value, e.kv.value);
return inserted;
}
/// ///
@nogc nothrow pure @safe unittest @nogc nothrow pure @safe unittest
{ {
@ -572,13 +601,15 @@ if (is(typeof(hasher(Key.init)) == size_t))
* Find the element with the key $(D_PARAM key). * Find the element with the key $(D_PARAM key).
* *
* Params: * Params:
* T = Type comparable with the key type, used for the lookup.
* key = The key to be find. * key = The key to be find.
* *
* Returns: The value associated with $(D_PARAM key). * Returns: The value associated with $(D_PARAM key).
* *
* Precondition: Element with $(D_PARAM key) is in this hash table. * Precondition: Element with $(D_PARAM key) is in this hash table.
*/ */
ref Value opIndex(Key key) ref Value opIndex(T)(auto ref const T key)
if (ifTestable!(T, a => Key.init == a))
{ {
const code = this.data.locateBucket(key); const code = this.data.locateBucket(key);
@ -634,12 +665,14 @@ if (is(typeof(hasher(Key.init)) == size_t))
* Looks for $(D_PARAM key) in this hash table. * Looks for $(D_PARAM key) in this hash table.
* *
* Params: * Params:
* T = Type comparable with the key type, used for the lookup.
* key = The key to look for. * key = The key to look for.
* *
* Returns: $(D_KEYWORD true) if $(D_PARAM key) exists in the hash table, * Returns: $(D_KEYWORD true) if $(D_PARAM key) exists in the hash table,
* $(D_KEYWORD false) otherwise. * $(D_KEYWORD false) otherwise.
*/ */
bool opBinaryRight(string op : "in")(auto ref inout(Key) key) inout bool opBinaryRight(string op : "in", T)(auto ref const T key) const
if (ifTestable!(T, a => Key.init == a))
{ {
return key in this.data; return key in this.data;
} }
@ -739,6 +772,9 @@ if (is(typeof(hasher(Key.init)) == size_t))
static assert(is(HashTable!(string, int) a)); static assert(is(HashTable!(string, int) a));
static assert(is(const HashTable!(string, int))); static assert(is(const HashTable!(string, int)));
static assert(isForwardRange!(HashTable!(string, int).Range)); static assert(isForwardRange!(HashTable!(string, int).Range));
static assert(is(HashTable!(int, int, (ref const int) => size_t.init)));
static assert(is(HashTable!(int, int, (int) => size_t.init)));
} }
// Constructs by reference // Constructs by reference
@ -809,3 +845,36 @@ if (is(typeof(hasher(Key.init)) == size_t))
assert(hashtable[-1131293824] == 6); assert(hashtable[-1131293824] == 6);
} }
} }
@nogc nothrow pure @safe unittest
{
static struct String
{
bool opEquals(string) const @nogc nothrow pure @safe
{
return true;
}
bool opEquals(ref const string) const @nogc nothrow pure @safe
{
return true;
}
bool opEquals(String) const @nogc nothrow pure @safe
{
return true;
}
bool opEquals(ref const String) const @nogc nothrow pure @safe
{
return true;
}
size_t toHash() const @nogc nothrow pure @safe
{
return 0;
}
}
static assert(is(typeof("asdf" in HashTable!(String, int)())));
static assert(is(typeof(HashTable!(String, int)()["asdf"])));
}

View File

@ -15,7 +15,6 @@
*/ */
module tanya.container.set; module tanya.container.set;
import tanya.algorithm.mutation;
import tanya.container.array; import tanya.container.array;
import tanya.container.entry; import tanya.container.entry;
import tanya.hash.lookup; import tanya.hash.lookup;
@ -460,6 +459,17 @@ if (is(typeof(hasher(T.init)) == size_t))
* *
* Returns: Amount of new elements inserted. * Returns: Amount of new elements inserted.
*/ */
size_t insert(ref T value)
{
auto e = ((ref v) @trusted => &this.data.insert(v))(value);
if (e.status != BucketStatus.used)
{
e.moveKey(value);
return 1;
}
return 0;
}
size_t insert(T value) size_t insert(T value)
{ {
auto e = ((ref v) @trusted => &this.data.insert(v))(value); auto e = ((ref v) @trusted => &this.data.insert(v))(value);
@ -551,12 +561,14 @@ if (is(typeof(hasher(T.init)) == size_t))
* $(D_KEYWORD in) operator. * $(D_KEYWORD in) operator.
* *
* Params: * Params:
* U = Type comparable with the element type, used for the lookup.
* value = Element to be searched for. * value = Element to be searched for.
* *
* Returns: $(D_KEYWORD true) if the given element exists in the container, * Returns: $(D_KEYWORD true) if the given element exists in the container,
* $(D_KEYWORD false) otherwise. * $(D_KEYWORD false) otherwise.
*/ */
bool opBinaryRight(string op : "in")(auto ref inout(T) value) inout bool opBinaryRight(string op : "in", U)(auto ref const U value) const
if (ifTestable!(U, a => T.init == a))
{ {
return value in this.data; return value in this.data;
} }

View File

@ -31,6 +31,7 @@ import std.algorithm.mutation : bringToFront, copy;
import std.algorithm.searching; import std.algorithm.searching;
import tanya.algorithm.comparison; import tanya.algorithm.comparison;
import tanya.algorithm.mutation; import tanya.algorithm.mutation;
import tanya.hash.lookup;
import tanya.memory; import tanya.memory;
import tanya.meta.trait; import tanya.meta.trait;
import tanya.meta.transform; import tanya.meta.transform;
@ -1614,6 +1615,16 @@ struct String
assert(s == "Казнить, нельзя помиловать."); assert(s == "Казнить, нельзя помиловать.");
} }
/**
* Calculates the hash value for the string.
*
* Returns: Hash value for the string.
*/
size_t toHash() const @nogc nothrow pure @safe
{
return hash(get);
}
mixin DefaultAllocator; mixin DefaultAllocator;
} }