Attention: this can't return ref as default value can be rvalue
Lookup methods
Attention: you can't use this method in @nogc code. Usual aakey method. Throws exception if key not found
Modifiers
put pair (k,v) into hash. it must be @safe, it inherits @nogc properties from K and V It can resize hashtable it is overloaded or has too much deleted entries
Tests test immutable struct and class as Key type
import std.experimental.logger; globalLogLevel = LogLevel.info; () @nogc nothrow{ struct S { int s; } HashMap!(immutable S, int) hs1; immutable ss = S(1); hs1[ss] = 1; assert(ss in hs1 && *(ss in hs1) == 1); HashMap!(int, immutable S) hs2; hs2[1] = ss; assert(1 in hs2 && *(1 in hs2) == ss); assert(!(2 in hs2)); }(); // class class C { int v; this(int _v) pure inout { v = _v; } bool opEquals(const C o) pure const @safe @nogc nothrow { return v == o.v; } override hash_t toHash() const @safe @nogc { return hash_function(v); } } HashMap!(immutable C, int) hc1; immutable cc = new immutable C(1); hc1[cc] = 1; assert(hc1[cc] == 1); HashMap!(int, immutable C) hc2; hc2[1] = cc; assert(hc2[1] is cc);
Test if we can work with non-@nogc opEquals for class-key. opEquals anyway must be non-@system.
class c { int a; this(int a) { this.a = a; } override hash_t toHash() const pure @safe { int[] _ = [1, 2, 3]; // this cause GC return hash_function(a); } bool opEquals(const c other) const pure nothrow @safe { auto _ = [1, 2, 3]; // this cause GC return this is other || this.a == other.a; } } alias K = c; alias V = int; HashMap!(K, V) h; K k0 = new c(0); V v0 = 1; h.put(k0, v0); int* v = k0 in h; assert(v); assert(*v == 1);
test byKey, byValue, byPair
import std.algorithm; import std.array; import std.stdio; HashMap!(int, string) m; m[1] = "one"; m[2] = "two"; m[10] = "ten"; assert(equal(m.byKey.array.sort, [1, 2, 10])); assert(equal(m.byValue.array.sort, ["one", "ten", "two"])); assert(equal(m.byPair.map!"tuple(a.key, a.value)".array.sort, [tuple(1, "one"), tuple(2, "two"), tuple(10, "ten")])); m.remove(1); m.remove(10); assert(equal(m.byPair.map!"tuple(a.key, a.value)".array.sort, [tuple(2, "two")])); m.remove(2); assert(m.byPair.map!"tuple(a.key, a.value)".array.sort.length() == 0); m.remove(2); assert(m.byPair.map!"tuple(a.key, a.value)".array.sort.length() == 0);
compare equivalence to AA
import std.random; import std.array; import std.algorithm; import std.stdio; import std.experimental.logger; enum iterations = 400_000; globalLogLevel = LogLevel.info; HashMap!(int, int) hashMap; int[int] AA; auto rnd = Random(unpredictableSeed); foreach (i; 0 .. iterations) { int k = uniform(0, iterations, rnd); hashMap.put(k, i); AA[k] = i; } assert(equal(AA.keys().sort(), hashMap.byKey().array.sort())); assert(equal(AA.values().sort(), hashMap.byValue().array.sort())); assert(AA.length == hashMap.length);
check remove
// test removal while iterating import std.random; import std.array; import std.algorithm; import std.stdio; import std.experimental.logger; enum iterations = 400_000; globalLogLevel = LogLevel.info; HashMap!(int, int) hashMap; auto rnd = Random(unpredictableSeed); foreach (i; 0 .. iterations) { int k = uniform(0, iterations, rnd); hashMap[k] = i; } foreach (k; hashMap.byKey) { assert(hashMap.remove(k)); } assert(hashMap.length == 0);
test clear
// test clear HashMap!(int, int) hashMap; foreach (i; 0 .. 100) { hashMap[i] = i; } hashMap.clear(); assert(hashMap.length == 0); hashMap[1] = 1; assert(1 in hashMap && hashMap.length == 1);
test getOrAdd with value
// test of nogc getOrAdd HashMap!(int, int) hashMap; foreach (i; 0 .. 100) { hashMap[i] = i; } auto v = hashMap.getOrAdd(-1, -1); assert(-1 in hashMap && v == -1);
test getOrAdd with callable
// test of nogc getOrAdd with lazy default value HashMap!(int, int) hashMap; foreach (i; 0 .. 100) { hashMap[i] = i; } int v = hashMap.getOrAdd(-1, () => -1); assert(-1 in hashMap && v == -1); assert(hashMap.get(-1, 0) == -1); // key -1 is in hash, return value assert(hashMap.get(-2, 0) == 0); // key -2 not in map, return default value assert(hashMap.get(-3, () => 0) == 0); // ditto
test getOrAdd with complex data
import std.socket; HashMap!(string, Socket) socketPool; Socket s0 = socketPool.getOrAdd("http://example.com", () => new Socket(AddressFamily.INET, SocketType.STREAM)); assert(s0 !is null); assert(s0.addressFamily == AddressFamily.INET); Socket s1 = socketPool.getOrAdd("http://example.com", () => new Socket(AddressFamily.INET, SocketType.STREAM)); assert(s1 !is null); assert(s1 is s0);
test with real class (socket)
import std.socket; class Connection { Socket s; bool opEquals(const Connection other) const pure @safe { return s is other.s; } override hash_t toHash() const @safe { return hash_function(s.handle); } } HashMap!(Connection, string) socketPool;
test if we can handle some exotic value type
// test of nogc getOrAdd with lazy default value // corner case when V is callable alias F = int function() @safe @nogc nothrow; F one = function() { return 1; }; F two = function() { return 2; }; F three = function() { return 3; }; F four = function() { return 4; }; HashMap!(int, F) hashMap; hashMap.put(1, one); hashMap.put(2, two); auto p = 1 in hashMap; assert(p); assert((*p)() == 1); p = 2 in hashMap; assert(p); assert((*p)() == 2); auto f3 = hashMap.getOrAdd(3, () => function int() { return 3; }); // used as default() assert(f3() == 3); auto f4 = hashMap.getOrAdd(4, four); assert(f4() == 4);