Skip to content

Commit 27bf9f0

Browse files
Hackerpilotdlang-bot
authored andcommitted
Add byKey, byValue/opSlice, and byKeyValue to HashMap (#103)
Add byKey, byValue/opSlice, and byKeyValue to HashMap merged-on-behalf-of: Jonathan Crapuchettes <jcrapuchettes@users.noreply.github.com>
1 parent e74dd80 commit 27bf9f0

File tree

7 files changed

+255
-36
lines changed

7 files changed

+255
-36
lines changed

dub.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"DLang Community"
99
],
1010
"dependencies": {
11-
"stdx-allocator": "~>2.77.0"
11+
"stdx-allocator": "~>2.77.0"
1212
}
1313
}

src/containers/hashmap.d

Lines changed: 147 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module containers.hashmap;
1010
private import containers.internal.hash : generateHash;
1111
private import containers.internal.node : shouldAddGCRange;
1212
private import stdx.allocator.mallocator : Mallocator;
13-
private import std.traits : isBasicType;
13+
private import std.traits : isBasicType, Unqual;
1414

1515
/**
1616
* Associative array / hash map.
@@ -37,7 +37,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
3737
/**
3838
* Use the given `allocator` for allocations.
3939
*/
40-
this(Allocator allocator)
40+
this(Allocator allocator) pure nothrow @nogc @safe
4141
in
4242
{
4343
assert(allocator !is null, "Allocator must not be null");
@@ -85,12 +85,24 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
8585
}
8686
}
8787

88-
~this()
88+
~this() nothrow
89+
{
90+
scope (failure) assert(false);
91+
clear();
92+
}
93+
94+
/**
95+
* Removes all items from the map
96+
*/
97+
void clear()
8998
{
9099
import stdx.allocator : dispose;
100+
91101
static if (useGC)
92102
GC.removeRange(buckets.ptr);
93103
allocator.dispose(buckets);
104+
buckets = null;
105+
_length = 0;
94106
}
95107

96108
/**
@@ -206,7 +218,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
206218
/**
207219
* Supports $(B aa[key] = value;) syntax.
208220
*/
209-
void opIndexAssign(V value, K key)
221+
void opIndexAssign(V value, const K key)
210222
{
211223
insert(key, value);
212224
}
@@ -217,7 +229,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
217229
* Returns: pointer to the value corresponding to the given key,
218230
* or null if the key is not present in the HashMap.
219231
*/
220-
inout(V)* opBinaryRight(string op)(K key) inout nothrow if (op == "in")
232+
inout(V)* opBinaryRight(string op)(K key) inout nothrow @trusted if (op == "in")
221233
{
222234
if (_length == 0)
223235
return null;
@@ -274,6 +286,14 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
274286
return _length == 0;
275287
}
276288

289+
/**
290+
* Returns: a range of the keys in this map.
291+
*/
292+
auto byKey(this This)() inout @trusted
293+
{
294+
return Slice!(This, IterType.key)(cast(Unqual!(This)*) &this);
295+
}
296+
277297
/**
278298
* Returns: a GC-allocated array filled with the keys contained in this map.
279299
*/
@@ -294,6 +314,16 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
294314
return app.data;
295315
}
296316

317+
318+
/**
319+
* Returns: a range of the values in this map.
320+
*/
321+
auto byValue(this This)() inout @trusted
322+
{
323+
return Slice!(This, IterType.value)(cast(Unqual!(This)*) &this);
324+
}
325+
alias opSlice = byValue;
326+
297327
/**
298328
* Returns: a GC-allocated array containing the values contained in this map.
299329
*/
@@ -309,20 +339,29 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
309339
foreach (ref const bucket; buckets)
310340
{
311341
foreach (item; bucket.range)
312-
app.put(item.value);
342+
app.put(cast(ContainerElementType!(This, V)) item.value);
313343
}
314344
return app.data;
315345
}
316346

347+
/**
348+
* Returns: a range of the kev/value pairs in this map. The element type of
349+
* this range is a struct with `key` and `value` fields.
350+
*/
351+
auto byKeyValue(this This)() inout @trusted
352+
{
353+
return Slice!(This, IterType.both)(cast(Unqual!(This)*) &this);
354+
}
355+
317356
/**
318357
* Support for $(D foreach(key, value; aa) { ... }) syntax;
319358
*/
320-
int opApply(D)(D del) if(isOpApplyDelegate!(D, const(K), V))
359+
int opApply(D)(D del) //if(isOpApplyDelegate!(D, const(K), V))
321360
{
322361
int result = 0;
323362
foreach (ref bucket; buckets)
324363
{
325-
foreach (ref node; bucket.range)
364+
foreach (ref node; bucket[])
326365
{
327366
result = del(node.key, node.value);
328367
if (result != 0)
@@ -338,7 +377,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
338377
int result = 0;
339378
foreach (const ref bucket; buckets)
340379
{
341-
foreach (const ref node; bucket.range)
380+
foreach (const ref node; bucket[])
342381
{
343382
result = del(node.key, node.value);
344383
if (result != 0)
@@ -350,7 +389,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K
350389

351390
private:
352391

353-
import stdx.allocator : make;
392+
import stdx.allocator : make, makeArray;
354393
import containers.unrolledlist : UnrolledList;
355394
import containers.internal.storage_type : ContainerStorageType;
356395
import containers.internal.element_type : ContainerElementType;
@@ -360,6 +399,93 @@ private:
360399
enum bool useGC = supportGC && (shouldAddGCRange!K || shouldAddGCRange!V);
361400
alias Hash = typeof({ K k = void; return hashFunction(k); }());
362401

402+
enum IterType: ubyte
403+
{
404+
key, value, both
405+
}
406+
407+
static struct Slice(MapType, IterType Type)
408+
{
409+
static if (Type == IterType.both)
410+
{
411+
struct FrontType
412+
{
413+
ContainerElementType!(MapType, K) key;
414+
ContainerElementType!(MapType, V) value;
415+
}
416+
}
417+
else static if (Type == IterType.value)
418+
alias FrontType = ContainerElementType!(MapType, V);
419+
else static if (Type == IterType.key)
420+
alias FrontType = ContainerElementType!(MapType, K);
421+
else
422+
static assert(false);
423+
424+
Unqual!(MapType)* hm;
425+
size_t bucketIndex;
426+
typeof(hm.buckets[0].opSlice()) bucketRange;
427+
bool _empty;
428+
429+
this(Unqual!(MapType)* hm)
430+
{
431+
this.hm = hm;
432+
this.bucketIndex = 0;
433+
bucketRange = typeof(bucketRange).init;
434+
this._empty = false;
435+
436+
while (true)
437+
{
438+
if (bucketIndex >= hm.buckets.length)
439+
{
440+
_empty = true;
441+
break;
442+
}
443+
bucketRange = hm.buckets[bucketIndex][];
444+
if (bucketRange.empty)
445+
bucketIndex++;
446+
else
447+
break;
448+
}
449+
}
450+
451+
FrontType front()
452+
{
453+
static if (Type == IterType.both)
454+
return FrontType(cast(ContainerElementType!(MapType, K)) bucketRange.front.key,
455+
cast(ContainerElementType!(MapType, V)) bucketRange.front.value);
456+
else static if (Type == IterType.value)
457+
return cast(ContainerElementType!(MapType, V)) bucketRange.front.value;
458+
else static if (Type == IterType.key)
459+
return cast(ContainerElementType!(MapType, K)) bucketRange.front.key;
460+
else
461+
static assert(false);
462+
}
463+
464+
bool empty() const pure nothrow @nogc @property
465+
{
466+
return _empty;
467+
}
468+
469+
void popFront() pure nothrow @nogc
470+
{
471+
bucketRange.popFront();
472+
if (bucketRange.empty)
473+
{
474+
while (bucketRange.empty)
475+
{
476+
bucketIndex++;
477+
if (bucketIndex >= hm.buckets.length)
478+
{
479+
_empty = true;
480+
break;
481+
}
482+
else
483+
bucketRange = hm.buckets[bucketIndex][];
484+
}
485+
}
486+
}
487+
}
488+
363489
template isOpApplyDelegate(D, KT, VT)
364490
{
365491
import std.traits : isDelegate, isImplicitlyConvertible, isIntegral, Parameters, ReturnType;
@@ -374,8 +500,9 @@ private:
374500
void initialize(size_t bucketCount = 4)
375501
{
376502
import std.conv : emplace;
503+
assert((bucketCount & (bucketCount - 1)) == 0, "bucketCount must be a power of two");
377504

378-
buckets = (cast(Bucket*) allocator.allocate(bucketCount * Bucket.sizeof))[0 .. bucketCount];
505+
buckets = makeArray!Bucket(allocator, bucketCount);
379506
static if (useGC)
380507
GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof);
381508
foreach (ref bucket; buckets)
@@ -387,7 +514,7 @@ private:
387514
}
388515
}
389516

390-
Node* insert(K key, V value)
517+
Node* insert(const K key, V value)
391518
{
392519
if (buckets.length == 0)
393520
initialize();
@@ -436,7 +563,6 @@ private:
436563
*/
437564
void rehash() @trusted
438565
{
439-
// import stdx.allocator : make, dispose;
440566
import std.conv : emplace;
441567
immutable size_t newLength = buckets.length << 1;
442568
immutable size_t newSize = newLength * Bucket.sizeof;
@@ -533,6 +659,8 @@ private:
533659
unittest
534660
{
535661
import std.uuid : randomUUID;
662+
import std.algorithm.iteration : walkLength;
663+
536664
auto hm = HashMap!(string, int)(16);
537665
assert (hm.length == 0);
538666
assert (!hm.remove("abc"));
@@ -554,6 +682,12 @@ unittest
554682
assert (hm.length == 1001);
555683
assert (hm.keys().length == hm.length);
556684
assert (hm.values().length == hm.length);
685+
() @nogc {
686+
assert (hm.byKey().walkLength == hm.length);
687+
assert (hm.byValue().walkLength == hm.length);
688+
assert (hm[].walkLength == hm.length);
689+
assert (hm.byKeyValue().walkLength == hm.length);
690+
}();
557691
foreach (const ref string k, ref int v; hm) {}
558692

559693
auto hm2 = HashMap!(char, char)(4);

src/containers/internal/hash.d

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,29 @@
66
*/
77
module containers.internal.hash;
88

9-
hash_t generateHash(T)(T value) nothrow @trusted
9+
static if (hash_t.sizeof == 4)
1010
{
11-
import std.functional : unaryFun;
12-
hash_t h = typeid(T).getHash(&value);
13-
h ^= (h >>> 20) ^ (h >>> 12);
14-
return h ^ (h >>> 7) ^ (h >>> 4);
11+
hash_t generateHash(T)(T value) nothrow @trusted
12+
{
13+
return typeid(T).getHash(&value);
14+
}
15+
}
16+
else
17+
{
18+
hash_t generateHash(T)(T value) nothrow @trusted if (!is(T == string))
19+
{
20+
return typeid(T).getHash(&value);
21+
}
22+
23+
hash_t generateHash(T)(T value) pure nothrow @nogc @trusted if (is(T == string))
24+
{
25+
immutable ulong fullIterCount = value.length >>> 3;
26+
immutable ulong remainderStart = fullIterCount << 3;
27+
ulong h;
28+
foreach (c; (cast(ulong*) value.ptr)[0 .. fullIterCount])
29+
h = (h ^ c) ^ (h >>> 4);
30+
foreach (c; value[remainderStart .. $])
31+
h += (h << 7) + c;
32+
return h;
33+
}
1534
}

src/containers/openhashset.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ struct OpenHashSet(T, Allocator = Mallocator,
8585
}
8686
}
8787

88-
~this()
88+
~this() nothrow
8989
{
9090
static if (useGC)
9191
GC.removeRange(nodes.ptr);

src/containers/ttree.d

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@ private:
399399

400400
// If we're storing a struct that defines opCmp, don't compare pointers as
401401
// that is almost certainly not what the user intended.
402-
static if (is(typeof(less) == string ) && less == "a < b" && isPointer!T && __traits(hasMember, PointerTarget!T, "opCmp"))
402+
static if (is(typeof(less) == string ) && less == "a < b"
403+
&& isPointer!T && __traits(hasMember, PointerTarget!T, "opCmp"))
403404
alias _less = binaryFun!"a.opCmp(*b) < 0";
404405
else
405406
alias _less = binaryFun!less;
@@ -532,7 +533,7 @@ private:
532533
bool insert(T value, ref Node* root, AllocatorType allocator)
533534
in
534535
{
535-
static if (isPointer!T || is (T == class) || is (T == interface))
536+
static if (isPointer!T || is (T == class) || is (T == interface))
536537
assert (value !is null);
537538
}
538539
body

0 commit comments

Comments
 (0)