Add HashTable container
This commit is contained in:
parent
faf952b30e
commit
bdce5cda6a
337
source/tanya/container/hashtable.d
Normal file
337
source/tanya/container/hashtable.d
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash table.
|
||||||
|
*
|
||||||
|
* Copyright: Eugene Wissner 2018.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/hashtable.d,
|
||||||
|
* tanya/container/hashtable.d)
|
||||||
|
*/
|
||||||
|
module tanya.container.hashtable;
|
||||||
|
|
||||||
|
import tanya.container.array;
|
||||||
|
import tanya.container.entry;
|
||||||
|
import tanya.hash.lookup;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.range.primitive;
|
||||||
|
import tanya.typecons;
|
||||||
|
|
||||||
|
/*struct Range(T)
|
||||||
|
{
|
||||||
|
static if (is(T == const))
|
||||||
|
{
|
||||||
|
private alias Buckets = T.buckets.ConstRange;
|
||||||
|
private alias Bucket = typeof(T.buckets[0]).ConstRange;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
private alias Buckets = T.buckets.Range;
|
||||||
|
private alias Bucket = typeof(T.buckets[0]).Range;
|
||||||
|
}
|
||||||
|
private alias E = ElementType!Bucket;
|
||||||
|
|
||||||
|
private Buckets buckets;
|
||||||
|
private Bucket bucket;
|
||||||
|
|
||||||
|
private bool findNextBucket()
|
||||||
|
{
|
||||||
|
while (!this.buckets.empty)
|
||||||
|
{
|
||||||
|
if (!this.buckets.front.empty)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.buckets.popFront();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private this(Buckets buckets)
|
||||||
|
{
|
||||||
|
this.buckets = buckets;
|
||||||
|
this.bucket = findNextBucket() ? this.buckets.front[] : Bucket.init;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property Range save()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property bool empty() const
|
||||||
|
{
|
||||||
|
return this.buckets.empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property ref inout(E) front() inout
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!empty);
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
return this.bucket.front;
|
||||||
|
}
|
||||||
|
|
||||||
|
void popFront()
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!empty);
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
this.bucket = findNextBucket() ? this.buckets.front[] : Bucket.init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@nogc nothrow pure @safe unittest
|
||||||
|
{
|
||||||
|
static assert(is(HashTable!(string, int)));
|
||||||
|
static assert(is(const HashTable!(string, int)));
|
||||||
|
static assert(isForwardRange!(Range!(HashTable!(string, int))));
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash table.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* Key = Key type.
|
||||||
|
* Value = Value type.
|
||||||
|
* hasher = Hash function for $(D_PARAM Key).
|
||||||
|
*/
|
||||||
|
struct HashTable(Key, Value, alias hasher = hash)
|
||||||
|
if (is(typeof(hasher(Key.init)) == size_t))
|
||||||
|
{
|
||||||
|
/* Forward range for $(D_PSYMBOL HashTable).
|
||||||
|
alias Range = .Range!HashTable;
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
alias ConstRange = .Range!(const HashTable);*/
|
||||||
|
|
||||||
|
private Array!(Bucket!(Key, Value)) buckets;
|
||||||
|
|
||||||
|
private size_t length_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new hash table.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* size = Initial, approximate hash table size.
|
||||||
|
* allocator = Allocator.
|
||||||
|
*
|
||||||
|
* Precondition: `allocator !is null`.
|
||||||
|
*/
|
||||||
|
this(size_t size, shared Allocator allocator = defaultAllocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
this.buckets = typeof(this.buckets)(size, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
this(shared Allocator allocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
this.buckets = typeof(this.buckets)(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of elements in the container.
|
||||||
|
*
|
||||||
|
* Returns: The number of elements in the container.
|
||||||
|
*/
|
||||||
|
@property size_t length() const
|
||||||
|
{
|
||||||
|
return this.length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells whether the container contains any elements.
|
||||||
|
*
|
||||||
|
* Returns: Whether the container is empty.
|
||||||
|
*/
|
||||||
|
@property bool empty() const
|
||||||
|
{
|
||||||
|
return this.length_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all elements.
|
||||||
|
*/
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
this.buckets.clear();
|
||||||
|
this.length_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Used allocator.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
@property shared(Allocator) allocator() const
|
||||||
|
out (allocator)
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
return this.buckets.allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new value at $(D_PARAM key) or reassigns the element if
|
||||||
|
* $(D_PARAM key) already exists in the hash table.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* key = The key to insert the value at.
|
||||||
|
* value = The value to be inserted.
|
||||||
|
*
|
||||||
|
* Returns: Just inserted element.
|
||||||
|
*/
|
||||||
|
ref Value opIndexAssign(Value value, Key key)
|
||||||
|
{
|
||||||
|
const code = locateBucket(this.buckets, hasher(key));
|
||||||
|
|
||||||
|
foreach (ref e; this.buckets[code .. $])
|
||||||
|
{
|
||||||
|
if (e == key)
|
||||||
|
{
|
||||||
|
return e.value = value;
|
||||||
|
}
|
||||||
|
else if (e.status != BucketStatus.used) // Insert the value.
|
||||||
|
{
|
||||||
|
++this.length_;
|
||||||
|
e.key = key;
|
||||||
|
e.value = value;
|
||||||
|
return e.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++this.length_;
|
||||||
|
this.buckets.length = this.buckets.length + 1;
|
||||||
|
this.buckets[$ - 1] = Bucket!(Key, Value)(key, value);
|
||||||
|
return this.buckets[$ - 1].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the element with the key $(D_PARAM key).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* key = The key to be find.
|
||||||
|
*
|
||||||
|
* Returns: The value associated with $(D_PARAM key).
|
||||||
|
*
|
||||||
|
* Precondition: Element with $(D_PARAM key) is in this hash table.
|
||||||
|
*/
|
||||||
|
ref Value opIndex(Key key)
|
||||||
|
{
|
||||||
|
const code = locateBucket(this.buckets, hasher(key));
|
||||||
|
|
||||||
|
for (auto range = this.buckets[code .. $]; !range.empty; range.popFront())
|
||||||
|
{
|
||||||
|
if (key == range.front.key)
|
||||||
|
{
|
||||||
|
return range.front.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(false, "Range violation");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the element with the key $(D_PARAM key).
|
||||||
|
*
|
||||||
|
* The method returns the number of elements removed. Since
|
||||||
|
* the hash table contains only unique keys, $(D_PARAM remove) always
|
||||||
|
* returns `1` if an element with the $(D_PARAM key) was found, `0`
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* key = The key to be removed.
|
||||||
|
*
|
||||||
|
* Returns: Number of the removed elements.
|
||||||
|
*/
|
||||||
|
size_t remove(Key key)
|
||||||
|
{
|
||||||
|
const code = locateBucket(this.buckets, hasher(key));
|
||||||
|
|
||||||
|
for (auto range = this.buckets[code .. $]; !range.empty; range.popFront())
|
||||||
|
{
|
||||||
|
if (key == range.front.key)
|
||||||
|
{
|
||||||
|
range.front.status = BucketStatus.deleted;
|
||||||
|
--this.length_;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks for $(D_PARAM key) in this hash table.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* key = The key to look for.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if $(D_PARAM key) exists in the hash table,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool opBinaryRight(string op : "in")(Key key)
|
||||||
|
{
|
||||||
|
const code = locateBucket(this.buckets, hasher(key));
|
||||||
|
|
||||||
|
foreach (ref const e; this.buckets[code .. $])
|
||||||
|
{
|
||||||
|
if (key == e.key)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@nogc nothrow pure @safe unittest
|
||||||
|
{
|
||||||
|
auto dinos = HashTable!(string, int)(17);
|
||||||
|
assert(dinos.empty);
|
||||||
|
|
||||||
|
dinos["Euoplocephalus"] = 6;
|
||||||
|
dinos["Triceratops"] = 7;
|
||||||
|
dinos["Pachycephalosaurus"] = 6;
|
||||||
|
dinos["Shantungosaurus"] = 15;
|
||||||
|
dinos["Ornithominus"] = 4;
|
||||||
|
dinos["Tyrannosaurus"] = 12;
|
||||||
|
dinos["Deinonychus"] = 3;
|
||||||
|
dinos["Iguanodon"] = 9;
|
||||||
|
dinos["Stegosaurus"] = 6;
|
||||||
|
dinos["Brachiosaurus"] = 25;
|
||||||
|
|
||||||
|
assert(dinos.length == 10);
|
||||||
|
assert(dinos["Iguanodon"] == 9);
|
||||||
|
assert(dinos["Ornithominus"] == 4);
|
||||||
|
assert(dinos["Stegosaurus"] == 6);
|
||||||
|
assert(dinos["Euoplocephalus"] == 6);
|
||||||
|
assert(dinos["Deinonychus"] == 3);
|
||||||
|
assert(dinos["Tyrannosaurus"] == 12);
|
||||||
|
assert(dinos["Pachycephalosaurus"] == 6);
|
||||||
|
assert(dinos["Shantungosaurus"] == 15);
|
||||||
|
assert(dinos["Triceratops"] == 7);
|
||||||
|
assert(dinos["Brachiosaurus"] == 25);
|
||||||
|
|
||||||
|
assert("Shantungosaurus" in dinos);
|
||||||
|
assert("Ceratopsia" !in dinos);
|
||||||
|
|
||||||
|
dinos.clear();
|
||||||
|
assert(dinos.empty);
|
||||||
|
}
|
@ -16,6 +16,7 @@ module tanya.container;
|
|||||||
|
|
||||||
public import tanya.container.array;
|
public import tanya.container.array;
|
||||||
public import tanya.container.buffer;
|
public import tanya.container.buffer;
|
||||||
|
public import tanya.container.hashtable;
|
||||||
public import tanya.container.list;
|
public import tanya.container.list;
|
||||||
public import tanya.container.set;
|
public import tanya.container.set;
|
||||||
public import tanya.container.string;
|
public import tanya.container.string;
|
||||||
|
Loading…
Reference in New Issue
Block a user