ea71555c68ff7a6d4c4fa2db980990e96f41d8e0
1 (* Copyright (C) DooM 2D:Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE a_modes.inc}
17 {.$DEFINE RBHASH_DEBUG_RESIZE}
18 {.$DEFINE RBHASH_DEBUG_INSERT}
19 {.$DEFINE RBHASH_DEBUG_DELETE}
20 {.$DEFINE RBHASH_DEBUG_COMPACT}
21 {$IF DEFINED(D2F_DEBUG)}
22 {$DEFINE RBHASH_SANITY_CHECKS}
24 // hash table (robin hood)
31 // WARNING! don't put structures into hash, use ponters or ids!
32 generic THashBase
<KeyT
, ValueT
> = class(TObject
)
34 const InitSize
= {$IF DEFINED(D2F_DEBUG)}16{$ELSE}512{$ENDIF}; // *MUST* be power of two
35 const LoadFactorPrc
= 90; // it is ok for robin hood hashes
38 type THashFn
= function (constref o
: KeyT
): LongWord;
39 type TEquFn
= function (constref a
, b
: KeyT
): Boolean;
40 type TIteratorFn
= function (constref k
: KeyT
; constref v
: ValueT
): Boolean is nested
; // return `true` to stop
49 hash
: LongWord; // key hash or 0
50 nextFree
: PEntry
; // next free entry
55 TEntryArray
= array of TEntry
;
57 TValEnumerator
= record
59 mEntries
: TEntryArray
;
60 mFirstEntry
, mLastEntry
, cur
: Integer;
62 constructor Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
63 function MoveNext (): Boolean; inline;
64 function getCurrent (): ValueT
; inline;
65 property Current
: ValueT read getCurrent
;
68 TKeyEnumerator
= record
70 mEntries
: TEntryArray
;
71 mFirstEntry
, mLastEntry
, cur
: Integer;
73 constructor Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
74 function MoveNext (): Boolean; inline;
75 function getCurrent (): KeyT
; inline;
76 property Current
: KeyT read getCurrent
;
79 TKeyValEnumerator
= record
81 mEntries
: TEntryArray
;
82 mFirstEntry
, mLastEntry
, cur
: Integer;
84 constructor Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
85 function MoveNext (): Boolean; inline;
86 function getCurrent (): PEntry
; inline;
87 property Current
: PEntry read getCurrent
;
93 mBuckets
: array of PEntry
; // entries, points to mEntries elements
94 mBucketsUsed
: Integer;
95 mEntries
: TEntryArray
;
96 {$IFDEF RBHASH_SANITY_CHECKS}
97 mEntriesUsed
: Integer;
99 mFreeEntryHead
: PEntry
;
100 mFirstEntry
, mLastEntry
: Integer;
104 function allocEntry (): PEntry
;
105 procedure releaseEntry (e
: PEntry
);
107 //function distToStIdx (idx: LongWord): LongWord; inline;
109 procedure putEntryInternal (swpe
: PEntry
);
111 function getCapacity (): Integer; inline;
114 constructor Create (ahashfn
: THashFn
; aequfn
: TEquFn
);
115 destructor Destroy (); override;
118 procedure reset (); // don't shrink buckets
121 procedure compact (); // call this instead of `rehash()` after alot of deletions
123 function get (constref akey
: KeyT
; out rval
: ValueT
): Boolean; // `true`: found
124 function put (constref akey
: KeyT
; constref aval
: ValueT
): Boolean; // `true`: replaced
125 function has (constref akey
: KeyT
): Boolean; // `true`: found
126 function del (constref akey
: KeyT
): Boolean; // `true`: deleted
128 //WARNING! don't modify table in iterator (queries are ok, though)
129 function forEach (it
: TIteratorFn
): Boolean;
131 // default `for ... in` enums values
132 function GetEnumerator (): TValEnumerator
;
133 function byKey (): TKeyEnumerator
;
134 function byValue (): TValEnumerator
;
135 function byKeyValue (): TKeyValEnumerator
; // PEntry
137 property count
: Integer read mBucketsUsed
;
138 property capacity
: Integer read getCapacity
;
142 TJoaatHasher
= record
144 seed
: LongWord; // initial seed value; MUST BE FIRST
145 hash
: LongWord; // current value
148 constructor Create (aseed
: LongWord);
150 procedure reset (); inline; overload
;
151 procedure reset (aseed
: LongWord); inline; overload
;
153 procedure put (constref buf
; len
: LongWord);
155 // current hash value
156 // you can continue putting data, as this is not destructive
157 function value
: LongWord; inline;
162 THashIntInt
= specialize THashBase
<Integer, Integer>;
163 THashStrInt
= specialize THashBase
<AnsiString, Integer>;
165 function hashNewIntInt (): THashIntInt
;
166 function hashNewStrInt (): THashStrInt
;
169 function u32Hash (a
: LongWord): LongWord; inline;
170 function fnvHash (constref buf
; len
: LongWord): LongWord;
171 function joaatHash (constref buf
; len
: LongWord): LongWord;
173 function nextPOT (x
: LongWord): LongWord; inline;
177 function hiiequ (constref a
, b
: Integer): Boolean;
178 function hiihash (constref k
: Integer): LongWord;
187 // ////////////////////////////////////////////////////////////////////////// //
190 function nextPOT (x
: LongWord): LongWord; inline;
193 result
:= result
or (result
shr 1);
194 result
:= result
or (result
shr 2);
195 result
:= result
or (result
shr 4);
196 result
:= result
or (result
shr 8);
197 result
:= result
or (result
shr 16);
199 if (x
<> 0) and ((x
and (x
-1)) = 0) then result
:= result
and (not (result
shr 1)) else result
+= 1;
204 // ////////////////////////////////////////////////////////////////////////// //
205 function hiiequ (constref a
, b
: Integer): Boolean; begin result
:= (a
= b
); end;
206 function hsiequ (constref a
, b
: AnsiString): Boolean; begin result
:= (a
= b
); end;
210 function hiihash (constref k
: Integer): LongWord;
212 result
:= LongWord(k
);
213 result
-= (result
shl 6);
214 result
:= result
xor (result
shr 17);
215 result
-= (result
shl 9);
216 result
:= result
xor (result
shl 4);
217 result
-= (result
shl 3);
218 result
:= result
xor (result
shl 10);
219 result
:= result
xor (result
shr 15);
222 function hsihash (constref k
: AnsiString): LongWord;
224 if (Length(k
) > 0) then result
:= fnvHash(PAnsiChar(k
)^, Length(k
)) else result
:= 0;
229 function hashNewIntInt (): THashIntInt
;
231 result
:= THashIntInt
.Create(hiihash
, hiiequ
);
235 function hashNewStrInt (): THashStrInt
;
237 result
:= THashStrInt
.Create(hsihash
, hsiequ
);
241 // ////////////////////////////////////////////////////////////////////////// //
244 constructor TJoaatHasher
.Create (aseed
: LongWord);
250 procedure TJoaatHasher
.reset (); inline; overload
;
256 procedure TJoaatHasher
.reset (aseed
: LongWord); inline; overload
;
263 procedure TJoaatHasher
.put (constref buf
; len
: LongWord);
268 if (len
< 1) then exit
;
269 bytes
:= PByte(@buf
);
275 h
:= h
xor (h
shr 6);
283 function TJoaatHasher
.value
: LongWord; inline;
286 result
+= (result
shl 3);
287 result
:= result
xor (result
shr 11);
288 result
+= (result
shl 15);
293 function joaatHash (constref buf
; len
: LongWord): LongWord;
297 h
:= TJoaatHasher
.Create(0);
298 h
.put(PByte(@buf
)^, len
);
303 // ////////////////////////////////////////////////////////////////////////// //
306 // fnv-1a: http://www.isthe.com/chongo/tech/comp/fnv/
307 function fnvHash (constref buf
; len
: LongWord): LongWord;
312 result
:= 2166136261; // fnv offset basis
315 result
:= result
xor b
^;
316 result
:= result
*16777619; // 32-bit fnv prime
326 function u32Hash (a
: LongWord): LongWord; inline;
329 result
-= (result
shl 6);
330 result
:= result
xor (result
shr 17);
331 result
-= (result
shl 9);
332 result
:= result
xor (result
shl 4);
333 result
-= (result
shl 3);
334 result
:= result
xor (result
shl 10);
335 result
:= result
xor (result
shr 15);
340 // ////////////////////////////////////////////////////////////////////////// //
341 constructor THashBase
.Create (ahashfn
: THashFn
; aequfn
: TEquFn
);
343 if not assigned(ahashfn
) then raise Exception
.Create('cannot create hash without hash function');
344 if not assigned(aequfn
) then raise Exception
.Create('cannot create hash without equality function');
348 mSeed
:= u32Hash($29a);
354 destructor THashBase
.Destroy ();
362 procedure THashBase
.clear ();
366 SetLength(mBuckets
, InitSize
);
367 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
369 SetLength(mEntries
, Length(mBuckets
));
371 for idx := 0 to High(mEntries)-1 do
373 mEntries[idx].hash := 0;
374 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
376 mEntries[High(mEntries)].hash := 0;
377 mEntries[High(mEntries)].nextFree := nil;
380 for idx := 0 to High(mEntries) do
382 mEntries[idx].hash := 0;
383 mEntries[idx].nextFree := nil;
388 {$IFDEF RBHASH_SANITY_CHECKS}
391 mFreeEntryHead
:= nil; //@mEntries[0];
397 procedure THashBase
.reset ();
401 if (mBucketsUsed
> 0) then
403 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
405 for idx := 0 to High(mEntries)-1 do
407 mEntries[idx].hash := 0;
408 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
410 mEntries[High(mEntries)].hash := 0;
411 mEntries[High(mEntries)].nextFree := nil;
414 if (mFirstEntry >= 0) then
416 for idx := mFirstEntry to mLastEntry do
418 mEntries[idx].hash := 0;
419 mEntries[idx].nextFree := nil;
425 {$IFDEF RBHASH_SANITY_CHECKS}
428 mFreeEntryHead
:= nil; //@mEntries[0];
435 function THashBase
.getCapacity (): Integer; inline; begin result
:= Length(mBuckets
); end;
438 function THashBase
.allocEntry (): PEntry
;
442 if (mFreeEntryHead
= nil) then
444 if (mLastEntry
= High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (0.0)');
446 if (mFirstEntry
= -1) then
448 if (mLastEntry
<> 0) then raise Exception
.Create('internal error in hash entry allocator (0.1)');
451 result
:= @mEntries
[mLastEntry
];
452 result
.nextFree
:= nil; // just in case
453 {$IFDEF RBHASH_SANITY_CHECKS}
458 {$IFDEF RBHASH_SANITY_CHECKS}
459 if (mFreeEntryHead
= nil) then raise Exception
.Create('internal error in hash entry allocator (0)');
460 if (mFreeEntryHead
.hash
<> 0) then raise Exception
.Create('internal error in hash entry allocator (1)');
462 result
:= mFreeEntryHead
;
463 mFreeEntryHead
:= result
.nextFree
;
464 {$IFDEF RBHASH_SANITY_CHECKS}
467 result
.nextFree
:= nil; // just in case
468 // fix mFirstEntry and mLastEntry
469 idx
:= Integer((PtrUInt(result
)-PtrUInt(@mEntries
[0])) div sizeof(mEntries
[0]));
470 {$IFDEF RBHASH_SANITY_CHECKS}
471 if (idx
< 0) or (idx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid entry address)');
472 if (result
<> @mEntries
[idx
]) then raise Exception
.Create('internal error in hash entry allocator (wtf?!)');
474 if (mFirstEntry
< 0) or (idx
< mFirstEntry
) then mFirstEntry
:= idx
;
475 if (idx
> mLastEntry
) then mLastEntry
:= idx
;
479 procedure THashBase
.releaseEntry (e
: PEntry
);
483 {$IFDEF RBHASH_SANITY_CHECKS}
484 if (mEntriesUsed
= 0) then raise Exception
.Create('internal error in hash entry allocator');
485 if (mEntriesUsed
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash entry allocator (entry/bucket count mismatch)');
486 if (e
= nil) then raise Exception
.Create('internal error in hash entry allocator (trying to release nil entry)');
487 if (e
.hash
= 0) then raise Exception
.Create('internal error in hash entry allocator (trying to release unallocated entry)');
489 idx
:= Integer((PtrUInt(e
)-PtrUInt(@mEntries
[0])) div sizeof(mEntries
[0]));
490 {$IFDEF RBHASH_SANITY_CHECKS}
491 if (idx
< 0) or (idx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid entry address)');
492 if (e
<> @mEntries
[idx
]) then raise Exception
.Create('internal error in hash entry allocator (wtf?!)');
494 {$IFDEF RBHASH_SANITY_CHECKS}
498 e
.nextFree
:= mFreeEntryHead
;
499 mFreeEntryHead
:= e
; //idx;
500 // fix mFirstEntry and mLastEntry
501 {$IFDEF RBHASH_SANITY_CHECKS}
502 if (mFirstEntry
< 0) or (mLastEntry
< 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 0)');
504 if (mFirstEntry
= mLastEntry
) then
506 {$IFDEF RBHASH_SANITY_CHECKS}
507 if (mEntriesUsed
<> 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 1)');
514 {$IFDEF RBHASH_SANITY_CHECKS}
515 if (mEntriesUsed
= 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 2)');
517 // fix first entry index
518 if (idx
= mFirstEntry
) then
521 while (mEntries
[cidx
].hash
= 0) do Inc(cidx
);
522 {$IFDEF RBHASH_SANITY_CHECKS}
523 if (cidx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 3)');
527 // fix last entry index
528 if (idx
= mLastEntry
) then
531 while (mEntries
[cidx
].hash
= 0) do Dec(cidx
);
532 {$IFDEF RBHASH_SANITY_CHECKS}
533 if (cidx
< 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 3)');
542 function THashBase.distToStIdx (idx: LongWord): LongWord; inline;
544 {$IFDEF RBHASH_SANITY_CHECKS}
545 assert(idx < Length(mBuckets));
546 assert(mBuckets[idx] <> nil);
548 result := mBuckets[idx].hash and High(mBuckets);
549 if (result <= idx) then result := idx-result else result := idx+(Length(mBuckets)-result);
554 function THashBase
.has (constref akey
: KeyT
): Boolean;
556 khash
, idx
: LongWord;
557 dist
, pdist
: LongWord;
561 if (mBucketsUsed
= 0) then exit
;
563 bhigh
:= High(mBuckets
);
564 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
565 idx
:= khash
and bhigh
;
566 if (mBuckets
[idx
] = nil) then exit
;
568 for dist
:= 0 to bhigh
do
570 if (mBuckets
[idx
] = nil) then break
;
571 //pdist := distToStIdx(idx);
572 pdist
:= mBuckets
[idx
].hash
and bhigh
;
573 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
575 if (dist
> pdist
) then break
;
576 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
577 if result
then break
;
578 idx
:= (idx
+1) and bhigh
;
583 function THashBase
.get (constref akey
: KeyT
; out rval
: ValueT
): Boolean;
585 khash
, idx
: LongWord;
586 dist
, pdist
: LongWord;
590 if (mBucketsUsed
= 0) then begin rval
:= Default(ValueT
); exit
; end;
592 bhigh
:= High(mBuckets
);
593 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
594 idx
:= khash
and bhigh
;
595 if (mBuckets
[idx
] = nil) then begin rval
:= Default(ValueT
); exit
; end;
597 for dist
:= 0 to bhigh
do
599 if (mBuckets
[idx
] = nil) then break
;
600 //pdist := distToStIdx(idx);
601 pdist
:= mBuckets
[idx
].hash
and bhigh
;
602 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
604 if (dist
> pdist
) then break
;
605 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
608 rval
:= mBuckets
[idx
].value
;
611 idx
:= (idx
+1) and bhigh
;
614 if not result
then rval
:= Default(ValueT
); // just in case
618 procedure THashBase
.putEntryInternal (swpe
: PEntry
);
620 idx
, dist
, pcur
, pdist
: LongWord;
621 tmpe
: PEntry
; // current entry to swap (or nothing)
624 bhigh
:= High(mBuckets
);
625 idx
:= swpe
.hash
and bhigh
;
626 {$IFDEF RBHASH_DEBUG_INSERT}writeln('inserting key ', swpe
.key
, '; value=', swpe
.value
, '; wantidx=', idx
, '; bhigh=', bhigh
);{$ENDIF}
628 for dist
:= 0 to bhigh
do
630 if (mBuckets
[idx
] = nil) then
633 {$IFDEF RBHASH_DEBUG_INSERT}writeln(' inserted to ', idx
);{$ENDIF}
634 mBuckets
[idx
] := swpe
;
638 //pdist := distToStIdx(idx);
639 pdist
:= mBuckets
[idx
].hash
and bhigh
;
640 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
642 if (pcur
> pdist
) then
644 // swapping the current bucket with the one to insert
645 tmpe
:= mBuckets
[idx
];
646 mBuckets
[idx
] := swpe
;
650 idx
:= (idx
+1) and bhigh
;
656 function THashBase
.put (constref akey
: KeyT
; constref aval
: ValueT
): Boolean;
658 khash
, idx
, dist
, pdist
: LongWord;
659 swpe
: PEntry
= nil; // current entry to swap (or nothing)
661 newsz
, eidx
: Integer;
665 bhigh
:= High(mBuckets
);
666 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
667 idx
:= khash
and bhigh
;
669 // check if we already have this key
670 if (mBucketsUsed
<> 0) and (mBuckets
[idx
] <> nil) then
672 for dist
:= 0 to bhigh
do
674 if (mBuckets
[idx
] = nil) then break
;
675 //pdist := distToStIdx(idx);
676 pdist
:= mBuckets
[idx
].hash
and bhigh
;
677 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
679 if (dist
> pdist
) then break
;
680 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
684 //mBuckets[idx].key := akey;
685 mBuckets
[idx
].value
:= aval
;
688 idx
:= (idx
+1) and bhigh
;
692 // need to resize hash?
693 if (mBucketsUsed
>= (bhigh
+1)*LoadFactorPrc
div 100) then
695 newsz
:= Length(mBuckets
);
696 if (Length(mEntries
) <> newsz
) then raise Exception
.Create('internal error in hash table (resize)');
697 if (newsz
<= 1024*1024*1024) then newsz
*= 2 else raise Exception
.Create('hash table too big');
698 {$IFDEF RBHASH_DEBUG_RESIZE}
699 writeln('resizing hash; used=', mBucketsUsed
, '; total=', (bhigh
+1), '; maxload=', (bhigh
+1)*LoadFactorPrc
div 100, '; newsz=', newsz
);
701 SetLength(mBuckets
, newsz
);
702 // resize entries array
703 eidx
:= Length(mEntries
);
704 SetLength(mEntries
, newsz
);
705 while (eidx
< Length(mEntries
)) do begin mEntries
[eidx
].hash
:= 0; Inc(eidx
); end;
706 // mFreeEntryHead will be fixed in `rehash()`
709 // as seed was changed, recalc hash
710 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
714 swpe
:= allocEntry();
719 putEntryInternal(swpe
);
723 // see http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
724 function THashBase
.del (constref akey
: KeyT
): Boolean;
726 khash
, idx
, idxnext
, pdist
, dist
: LongWord;
730 if (mBucketsUsed
= 0) then exit
;
732 bhigh
:= High(mBuckets
);
733 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
734 idx
:= khash
and bhigh
;
737 if (mBuckets
[idx
] = nil) then exit
; // no key
738 for dist
:= 0 to bhigh
do
740 if (mBuckets
[idx
] = nil) then break
;
741 //pdist := distToStIdx(idxcur);
742 pdist
:= mBuckets
[idx
].hash
and bhigh
;
743 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
745 if (dist
> pdist
) then break
;
746 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
747 if result
then break
;
748 idx
:= (idx
+1) and bhigh
;
754 {$IFDEF RBHASH_DEBUG_DELETE}
755 writeln('del: key ', akey
, ': not found');
760 {$IFDEF RBHASH_DEBUG_DELETE}
761 writeln('del: key ', akey
, ': found at ', idx
, '; ek=', mBuckets
[idx
].key
, '; ev=', mBuckets
[idx
].value
);
763 releaseEntry(mBuckets
[idx
]);
765 idxnext
:= (idx
+1) and bhigh
;
766 for dist
:= 0 to bhigh
do
768 {$IFDEF RBHASH_DEBUG_DELETE}
769 writeln(' dist=', dist
, '; idx=', idx
, '; idxnext=', idxnext
, '; ce=', (mBuckets
[idx
] <> nil), '; ne=', (mBuckets
[idxnext
] <> nil));
771 if (mBuckets
[idxnext
] = nil) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' idxnext nil');{$ENDIF} mBuckets
[idx
] := nil; break
; end;
772 //pdist := distToStIdx(idxnext);
773 pdist
:= mBuckets
[idxnext
].hash
and bhigh
;
774 if (pdist
<= idxnext
) then pdist
:= idxnext
-pdist
else pdist
:= idxnext
+((bhigh
+1)-pdist
);
776 if (pdist
= 0) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist is zero');{$ENDIF} mBuckets
[idx
] := nil; break
; end;
777 {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist=', pdist
);{$ENDIF}
778 mBuckets
[idx
] := mBuckets
[idxnext
];
779 idx
:= (idx
+1) and bhigh
;
780 idxnext
:= (idxnext
+1) and bhigh
;
787 procedure THashBase
.rehash ();
791 e
: PEntry
= nil; // shut up, fpc!
792 {$IFDEF RBHASH_SANITY_CHECKS}
796 // change seed, to minimize pathological cases
797 if (mSeed
= 0) then mSeed
:= $29a;
798 mSeed
:= u32Hash(mSeed
);
800 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
803 mFreeEntryHead
:= nil;
805 for idx
:= 0 to High(mEntries
) do
808 if (e
.hash
<> 0) then
810 {$IFDEF RBHASH_SANITY_CHECKS}
811 if (e
.nextFree
<> nil) then raise Exception
.Create('internal error in rehash: inconsistent');
812 if (cnt
= 0) and (idx
<> mFirstEntry
) then raise Exception
.Create('internal error in rehash: inconsistent (1)');
814 if (cnt
= mBucketsUsed
) and (idx
<> mLastEntry
) then raise Exception
.Create('internal error in rehash: inconsistent (2)');
816 e
.hash
:= hashfn(e
.key
) xor mSeed
; if (e
.hash
= 0) then e
.hash
:= $29a;
821 if (lastfree
<> nil) then lastfree
.nextFree
:= e
else mFreeEntryHead
:= e
;
825 if (lastfree
<> nil) then e
.nextFree
:= nil;
826 {$IFDEF RBHASH_SANITY_CHECKS}
827 if (cnt
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table resize (invalid first/last range; 0)');
828 if (cnt
<> mEntriesUsed
) then raise Exception
.Create('internal error in hash table resize (invalid first/last range; 1)');
833 procedure THashBase
.compact ();
835 newsz
, didx
, f
: Integer;
836 {$IFDEF RBHASH_SANITY_CHECKS}
840 newsz
:= nextPOT(LongWord(mBucketsUsed
));
841 if (newsz
>= 1024*1024*1024) then exit
;
842 if (newsz
*2 >= Length(mBuckets
)) then exit
;
843 if (newsz
*2 < 128) then exit
;
844 {$IFDEF RBHASH_DEBUG_COMPACT}writeln('compacting; used=', mBucketsUsed
, '; oldsizePOT=', newsz
, '; newsize=', newsz
*2);{$ENDIF}
846 // move all entries to top
847 if (mFirstEntry
>= 0) then
849 {$IFDEF RBHASH_SANITY_CHECKS}
850 if (mBucketsUsed
< 1) then raise Exception
.Create('internal error in hash table (invalid bucket count; 0)');
853 while (didx
< Length(mEntries
)) do if (mEntries
[didx
].hash
<> 0) then Inc(didx
) else break
;
858 if (mEntries
[f
].hash
<> 0) then
860 {$IFDEF RBHASH_SANITY_CHECKS}
861 if (didx
>= f
) then raise Exception
.Create('internal error in hash: inconsistent');
863 mEntries
[didx
] := mEntries
[f
];
864 mEntries
[f
].hash
:= 0;
866 if (f
= mLastEntry
) then break
;
867 while (didx
< Length(mEntries
)) do if (mEntries
[didx
].hash
<> 0) then Inc(didx
) else break
;
871 {$IFDEF RBHASH_SANITY_CHECKS}
872 if (didx
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 1)');
875 mLastEntry
:= mBucketsUsed
-1;
876 {$IFDEF RBHASH_SANITY_CHECKS}
878 for f
:= mFirstEntry
to mLastEntry
do
880 if (mEntries
[f
].hash
= 0) then raise Exception
.Create('internal error in hash table (invalid first/last range; 2)');
883 if (cnt
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 3)');
884 if (cnt
<> mEntriesUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 4)');
885 for f
:= mLastEntry
+1 to High(mEntries
) do
887 if (mEntries
[f
].hash
<> 0) then raise Exception
.Create('internal error in hash table (invalid first/last range; 5)');
893 {$IFDEF RBHASH_SANITY_CHECKS}
894 if (mBucketsUsed
<> 0) then raise Exception
.Create('internal error in hash table (invalid bucket count; 1)');
898 SetLength(mBuckets
, newsz
);
899 SetLength(mEntries
, newsz
);
900 // mFreeEntryHead will be fixed in `rehash()`
906 function THashBase
.forEach (it
: TIteratorFn
): Boolean;
911 if not assigned(it
) then exit
;
913 if (i
< 0) then exit
;
914 while (i
<= mLastEntry
) do
916 if (mEntries
[i
].hash
<> 0) then
918 result
:= it(mEntries
[i
].key
, mEntries
[i
].value
);
927 function THashBase
.GetEnumerator (): TValEnumerator
;
929 if (Length(mEntries
) > 0) then result
:= TValEnumerator
.Create(mEntries
, mFirstEntry
, mLastEntry
)
930 else result
:= TValEnumerator
.Create(nil, -1, -1);
933 function THashBase
.byKey (): TKeyEnumerator
;
935 if (Length(mEntries
) > 0) then result
:= TKeyEnumerator
.Create(mEntries
, mFirstEntry
, mLastEntry
)
936 else result
:= TKeyEnumerator
.Create(nil, -1, -1);
939 function THashBase
.byValue (): TValEnumerator
;
941 if (Length(mEntries
) > 0) then result
:= TValEnumerator
.Create(mEntries
, mFirstEntry
, mLastEntry
)
942 else result
:= TValEnumerator
.Create(nil, -1, -1);
945 function THashBase
.byKeyValue (): TKeyValEnumerator
; // PEntry
947 if (Length(mEntries
) > 0) then result
:= TKeyValEnumerator
.Create(mEntries
, mFirstEntry
, mLastEntry
)
948 else result
:= TKeyValEnumerator
.Create(nil, -1, -1);
952 // ////////////////////////////////////////////////////////////////////////// //
953 constructor THashBase
.TValEnumerator
.Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
956 mFirstEntry
:= afirst
;
958 cur
:= mFirstEntry
-1;
961 function THashBase
.TValEnumerator
.MoveNext (): Boolean; inline;
964 while (cur
<= mLastEntry
) do
966 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
971 function THashBase
.TValEnumerator
.getCurrent (): ValueT
; inline;
973 result
:= mEntries
[cur
].value
;
977 // ////////////////////////////////////////////////////////////////////////// //
978 constructor THashBase
.TKeyEnumerator
.Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
981 mFirstEntry
:= afirst
;
983 cur
:= mFirstEntry
-1;
986 function THashBase
.TKeyEnumerator
.MoveNext (): Boolean; inline;
989 while (cur
<= mLastEntry
) do
991 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
996 function THashBase
.TKeyEnumerator
.getCurrent (): KeyT
; inline;
998 result
:= mEntries
[cur
].key
;
1002 // ////////////////////////////////////////////////////////////////////////// //
1003 constructor THashBase
.TKeyValEnumerator
.Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
1006 mFirstEntry
:= afirst
;
1007 mLastEntry
:= alast
;
1008 cur
:= mFirstEntry
-1;
1011 function THashBase
.TKeyValEnumerator
.MoveNext (): Boolean; inline;
1014 while (cur
<= mLastEntry
) do
1016 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
1021 function THashBase
.TKeyValEnumerator
.getCurrent (): PEntry
; inline;
1023 result
:= @mEntries
[cur
];