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 TValEnumerator
= record
58 mFirstEntry
, mLastEntry
, cur
: Integer;
60 constructor Create (aents
: PEntry
; afirst
, alast
: Integer);
61 function MoveNext
: Boolean;
62 function getCurrent (): ValueT
;
63 property Current
: ValueT read getCurrent
;
66 TKeyEnumerator
= record
69 mFirstEntry
, mLastEntry
, cur
: Integer;
71 constructor Create (aents
: PEntry
; afirst
, alast
: Integer);
72 function MoveNext
: Boolean;
73 function getCurrent (): KeyT
;
74 property Current
: KeyT read getCurrent
;
77 TKeyValEnumerator
= record
80 mFirstEntry
, mLastEntry
, cur
: Integer;
82 constructor Create (aents
: PEntry
; afirst
, alast
: Integer);
83 function MoveNext
: Boolean;
84 function getCurrent (): PEntry
;
85 property Current
: PEntry read getCurrent
;
91 mBuckets
: array of PEntry
; // entries, points to mEntries elements
92 mBucketsUsed
: Integer;
93 mEntries
: array of TEntry
;
94 {$IFDEF RBHASH_SANITY_CHECKS}
95 mEntriesUsed
: Integer;
97 mFreeEntryHead
: PEntry
;
98 mFirstEntry
, mLastEntry
: Integer;
102 function allocEntry (): PEntry
;
103 procedure releaseEntry (e
: PEntry
);
105 //function distToStIdx (idx: LongWord): LongWord; inline;
107 procedure putEntryInternal (swpe
: PEntry
);
109 function getCapacity (): Integer; inline;
112 constructor Create (ahashfn
: THashFn
; aequfn
: TEquFn
);
113 destructor Destroy (); override;
116 procedure reset (); // don't shrink buckets
119 procedure compact (); // call this instead of `rehash()` after alot of deletions
121 function get (constref akey
: KeyT
; out rval
: ValueT
): Boolean; // `true`: found
122 function put (constref akey
: KeyT
; constref aval
: ValueT
): Boolean; // `true`: replaced
123 function has (constref akey
: KeyT
): Boolean; // `true`: found
124 function del (constref akey
: KeyT
): Boolean; // `true`: deleted
126 //WARNING! don't modify table in iterator (queries are ok, though)
127 function forEach (it
: TIteratorFn
): Boolean;
129 // default `for ... in` enums values
130 function GetEnumerator (): TValEnumerator
;
131 function byKey (): TKeyEnumerator
;
132 function byValue (): TValEnumerator
;
133 function byKeyValue (): TKeyValEnumerator
; // PEntry
135 property count
: Integer read mBucketsUsed
;
136 property capacity
: Integer read getCapacity
;
140 TJoaatHasher
= record
142 seed
: LongWord; // initial seed value; MUST BE FIRST
143 hash
: LongWord; // current value
146 constructor Create (aseed
: LongWord);
148 procedure reset (); inline; overload
;
149 procedure reset (aseed
: LongWord); inline; overload
;
151 procedure put (constref buf
; len
: LongWord);
153 // current hash value
154 // you can continue putting data, as this is not destructive
155 function value
: LongWord; inline;
160 THashIntInt
= specialize THashBase
<Integer, Integer>;
161 THashStrInt
= specialize THashBase
<AnsiString, Integer>;
163 function hashNewIntInt (): THashIntInt
;
164 function hashNewStrInt (): THashStrInt
;
167 function u32Hash (a
: LongWord): LongWord; inline;
168 function fnvHash (constref buf
; len
: LongWord): LongWord;
169 function joaatHash (constref buf
; len
: LongWord): LongWord;
171 function nextPOT (x
: LongWord): LongWord; inline;
180 // ////////////////////////////////////////////////////////////////////////// //
183 function nextPOT (x
: LongWord): LongWord; inline;
186 result
:= result
or (result
shr 1);
187 result
:= result
or (result
shr 2);
188 result
:= result
or (result
shr 4);
189 result
:= result
or (result
shr 8);
190 result
:= result
or (result
shr 16);
192 if (x
<> 0) and ((x
and (x
-1)) = 0) then result
:= result
and (not (result
shr 1)) else result
+= 1;
197 // ////////////////////////////////////////////////////////////////////////// //
198 function hiiequ (constref a
, b
: Integer): Boolean; begin result
:= (a
= b
); end;
199 function hsiequ (constref a
, b
: AnsiString): Boolean; begin result
:= (a
= b
); end;
203 function hiihash (constref k
: Integer): LongWord;
206 result
-= (result
shl 6);
207 result
:= result
xor (result
shr 17);
208 result
-= (result
shl 9);
209 result
:= result
xor (result
shl 4);
210 result
-= (result
shl 3);
211 result
:= result
xor (result
shl 10);
212 result
:= result
xor (result
shr 15);
215 function hsihash (constref k
: AnsiString): LongWord;
217 if (Length(k
) > 0) then result
:= fnvHash(PAnsiChar(k
)^, Length(k
)) else result
:= 0;
222 function hashNewIntInt (): THashIntInt
;
224 result
:= THashIntInt
.Create(hiihash
, hiiequ
);
228 function hashNewStrInt (): THashStrInt
;
230 result
:= THashStrInt
.Create(hsihash
, hsiequ
);
234 // ////////////////////////////////////////////////////////////////////////// //
237 constructor TJoaatHasher
.Create (aseed
: LongWord);
243 procedure TJoaatHasher
.reset (); inline; overload
;
249 procedure TJoaatHasher
.reset (aseed
: LongWord); inline; overload
;
256 procedure TJoaatHasher
.put (constref buf
; len
: LongWord);
261 if (len
< 1) then exit
;
262 bytes
:= PByte(@buf
);
268 h
:= h
xor (h
shr 6);
276 function TJoaatHasher
.value
: LongWord; inline;
279 result
+= (result
shl 3);
280 result
:= result
xor (result
shr 11);
281 result
+= (result
shl 15);
286 function joaatHash (constref buf
; len
: LongWord): LongWord;
290 h
:= TJoaatHasher
.Create(0);
291 h
.put(PByte(@buf
)^, len
);
296 // ////////////////////////////////////////////////////////////////////////// //
299 // fnv-1a: http://www.isthe.com/chongo/tech/comp/fnv/
300 function fnvHash (constref buf
; len
: LongWord): LongWord;
305 result
:= 2166136261; // fnv offset basis
308 result
:= result
xor b
^;
309 result
:= result
*16777619; // 32-bit fnv prime
319 function u32Hash (a
: LongWord): LongWord; inline;
322 result
-= (result
shl 6);
323 result
:= result
xor (result
shr 17);
324 result
-= (result
shl 9);
325 result
:= result
xor (result
shl 4);
326 result
-= (result
shl 3);
327 result
:= result
xor (result
shl 10);
328 result
:= result
xor (result
shr 15);
333 // ////////////////////////////////////////////////////////////////////////// //
334 constructor THashBase
.Create (ahashfn
: THashFn
; aequfn
: TEquFn
);
336 if not assigned(ahashfn
) then raise Exception
.Create('cannot create hash without hash function');
337 if not assigned(aequfn
) then raise Exception
.Create('cannot create hash without equality function');
341 mSeed
:= u32Hash($29a);
347 destructor THashBase
.Destroy ();
355 procedure THashBase
.clear ();
359 SetLength(mBuckets
, InitSize
);
360 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
362 SetLength(mEntries
, Length(mBuckets
));
364 for idx := 0 to High(mEntries)-1 do
366 mEntries[idx].hash := 0;
367 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
369 mEntries[High(mEntries)].hash := 0;
370 mEntries[High(mEntries)].nextFree := nil;
373 for idx := 0 to High(mEntries) do
375 mEntries[idx].hash := 0;
376 mEntries[idx].nextFree := nil;
381 {$IFDEF RBHASH_SANITY_CHECKS}
384 mFreeEntryHead
:= nil; //@mEntries[0];
390 procedure THashBase
.reset ();
394 if (mBucketsUsed
> 0) then
396 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
398 for idx := 0 to High(mEntries)-1 do
400 mEntries[idx].hash := 0;
401 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
403 mEntries[High(mEntries)].hash := 0;
404 mEntries[High(mEntries)].nextFree := nil;
407 if (mFirstEntry >= 0) then
409 for idx := mFirstEntry to mLastEntry do
411 mEntries[idx].hash := 0;
412 mEntries[idx].nextFree := nil;
418 {$IFDEF RBHASH_SANITY_CHECKS}
421 mFreeEntryHead
:= nil; //@mEntries[0];
428 function THashBase
.getCapacity (): Integer; inline; begin result
:= Length(mBuckets
); end;
431 function THashBase
.allocEntry (): PEntry
;
435 if (mFreeEntryHead
= nil) then
437 if (mLastEntry
= High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (0.0)');
439 if (mFirstEntry
= -1) then
441 if (mLastEntry
<> 0) then raise Exception
.Create('internal error in hash entry allocator (0.1)');
444 result
:= @mEntries
[mLastEntry
];
445 result
.nextFree
:= nil; // just in case
446 {$IFDEF RBHASH_SANITY_CHECKS}
451 {$IFDEF RBHASH_SANITY_CHECKS}
452 if (mFreeEntryHead
= nil) then raise Exception
.Create('internal error in hash entry allocator (0)');
453 if (mFreeEntryHead
.hash
<> 0) then raise Exception
.Create('internal error in hash entry allocator (1)');
455 result
:= mFreeEntryHead
;
456 mFreeEntryHead
:= result
.nextFree
;
457 {$IFDEF RBHASH_SANITY_CHECKS}
460 result
.nextFree
:= nil; // just in case
461 // fix mFirstEntry and mLastEntry
462 idx
:= Integer((PtrUInt(result
)-PtrUInt(@mEntries
[0])) div sizeof(mEntries
[0]));
463 {$IFDEF RBHASH_SANITY_CHECKS}
464 if (idx
< 0) or (idx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid entry address)');
465 if (result
<> @mEntries
[idx
]) then raise Exception
.Create('internal error in hash entry allocator (wtf?!)');
467 if (mFirstEntry
< 0) or (idx
< mFirstEntry
) then mFirstEntry
:= idx
;
468 if (idx
> mLastEntry
) then mLastEntry
:= idx
;
472 procedure THashBase
.releaseEntry (e
: PEntry
);
476 {$IFDEF RBHASH_SANITY_CHECKS}
477 if (mEntriesUsed
= 0) then raise Exception
.Create('internal error in hash entry allocator');
478 if (mEntriesUsed
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash entry allocator (entry/bucket count mismatch)');
479 if (e
= nil) then raise Exception
.Create('internal error in hash entry allocator (trying to release nil entry)');
480 if (e
.hash
= 0) then raise Exception
.Create('internal error in hash entry allocator (trying to release unallocated entry)');
482 idx
:= Integer((PtrUInt(e
)-PtrUInt(@mEntries
[0])) div sizeof(mEntries
[0]));
483 {$IFDEF RBHASH_SANITY_CHECKS}
484 if (idx
< 0) or (idx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid entry address)');
485 if (e
<> @mEntries
[idx
]) then raise Exception
.Create('internal error in hash entry allocator (wtf?!)');
487 {$IFDEF RBHASH_SANITY_CHECKS}
491 e
.nextFree
:= mFreeEntryHead
;
492 mFreeEntryHead
:= e
; //idx;
493 // fix mFirstEntry and mLastEntry
494 {$IFDEF RBHASH_SANITY_CHECKS}
495 if (mFirstEntry
< 0) or (mLastEntry
< 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 0)');
497 if (mFirstEntry
= mLastEntry
) then
499 {$IFDEF RBHASH_SANITY_CHECKS}
500 if (mEntriesUsed
<> 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 1)');
507 {$IFDEF RBHASH_SANITY_CHECKS}
508 if (mEntriesUsed
= 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 2)');
510 // fix first entry index
511 if (idx
= mFirstEntry
) then
514 while (mEntries
[cidx
].hash
= 0) do Inc(cidx
);
515 {$IFDEF RBHASH_SANITY_CHECKS}
516 if (cidx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 3)');
520 // fix last entry index
521 if (idx
= mLastEntry
) then
524 while (mEntries
[cidx
].hash
= 0) do Dec(cidx
);
525 {$IFDEF RBHASH_SANITY_CHECKS}
526 if (cidx
< 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 3)');
535 function THashBase.distToStIdx (idx: LongWord): LongWord; inline;
537 {$IFDEF RBHASH_SANITY_CHECKS}
538 assert(idx < Length(mBuckets));
539 assert(mBuckets[idx] <> nil);
541 result := mBuckets[idx].hash and High(mBuckets);
542 if (result <= idx) then result := idx-result else result := idx+(Length(mBuckets)-result);
547 function THashBase
.has (constref akey
: KeyT
): Boolean;
549 khash
, idx
: LongWord;
550 dist
, pdist
: LongWord;
554 if (mBucketsUsed
= 0) then exit
;
556 bhigh
:= High(mBuckets
);
557 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
558 idx
:= khash
and bhigh
;
559 if (mBuckets
[idx
] = nil) then exit
;
561 for dist
:= 0 to bhigh
do
563 if (mBuckets
[idx
] = nil) then break
;
564 //pdist := distToStIdx(idx);
565 pdist
:= mBuckets
[idx
].hash
and bhigh
;
566 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
568 if (dist
> pdist
) then break
;
569 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
570 if result
then break
;
571 idx
:= (idx
+1) and bhigh
;
576 function THashBase
.get (constref akey
: KeyT
; out rval
: ValueT
): Boolean;
578 khash
, idx
: LongWord;
579 dist
, pdist
: LongWord;
583 if (mBucketsUsed
= 0) then begin rval
:= Default(ValueT
); exit
; end;
585 bhigh
:= High(mBuckets
);
586 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
587 idx
:= khash
and bhigh
;
588 if (mBuckets
[idx
] = nil) then begin rval
:= Default(ValueT
); exit
; end;
590 for dist
:= 0 to bhigh
do
592 if (mBuckets
[idx
] = nil) then break
;
593 //pdist := distToStIdx(idx);
594 pdist
:= mBuckets
[idx
].hash
and bhigh
;
595 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
597 if (dist
> pdist
) then break
;
598 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
601 rval
:= mBuckets
[idx
].value
;
604 idx
:= (idx
+1) and bhigh
;
607 if not result
then rval
:= Default(ValueT
); // just in case
611 procedure THashBase
.putEntryInternal (swpe
: PEntry
);
613 idx
, dist
, pcur
, pdist
: LongWord;
614 tmpe
: PEntry
; // current entry to swap (or nothing)
617 bhigh
:= High(mBuckets
);
618 idx
:= swpe
.hash
and bhigh
;
619 {$IFDEF RBHASH_DEBUG_INSERT}writeln('inserting key ', swpe
.key
, '; value=', swpe
.value
, '; wantidx=', idx
, '; bhigh=', bhigh
);{$ENDIF}
621 for dist
:= 0 to bhigh
do
623 if (mBuckets
[idx
] = nil) then
626 {$IFDEF RBHASH_DEBUG_INSERT}writeln(' inserted to ', idx
);{$ENDIF}
627 mBuckets
[idx
] := swpe
;
631 //pdist := distToStIdx(idx);
632 pdist
:= mBuckets
[idx
].hash
and bhigh
;
633 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
635 if (pcur
> pdist
) then
637 // swapping the current bucket with the one to insert
638 tmpe
:= mBuckets
[idx
];
639 mBuckets
[idx
] := swpe
;
643 idx
:= (idx
+1) and bhigh
;
649 function THashBase
.put (constref akey
: KeyT
; constref aval
: ValueT
): Boolean;
651 khash
, idx
, dist
, pdist
: LongWord;
652 swpe
: PEntry
= nil; // current entry to swap (or nothing)
654 newsz
, eidx
: Integer;
658 bhigh
:= High(mBuckets
);
659 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
660 idx
:= khash
and bhigh
;
662 // check if we already have this key
663 if (mBucketsUsed
<> 0) and (mBuckets
[idx
] <> nil) then
665 for dist
:= 0 to bhigh
do
667 if (mBuckets
[idx
] = nil) then break
;
668 //pdist := distToStIdx(idx);
669 pdist
:= mBuckets
[idx
].hash
and bhigh
;
670 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
672 if (dist
> pdist
) then break
;
673 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
677 //mBuckets[idx].key := akey;
678 mBuckets
[idx
].value
:= aval
;
681 idx
:= (idx
+1) and bhigh
;
685 // need to resize hash?
686 if (mBucketsUsed
>= (bhigh
+1)*LoadFactorPrc
div 100) then
688 newsz
:= Length(mBuckets
);
689 if (Length(mEntries
) <> newsz
) then raise Exception
.Create('internal error in hash table (resize)');
690 if (newsz
<= 1024*1024*1024) then newsz
*= 2 else raise Exception
.Create('hash table too big');
691 {$IFDEF RBHASH_DEBUG_RESIZE}
692 writeln('resizing hash; used=', mBucketsUsed
, '; total=', (bhigh
+1), '; maxload=', (bhigh
+1)*LoadFactorPrc
div 100, '; newsz=', newsz
);
694 SetLength(mBuckets
, newsz
);
695 // resize entries array
696 eidx
:= Length(mEntries
);
697 SetLength(mEntries
, newsz
);
698 while (eidx
< Length(mEntries
)) do begin mEntries
[eidx
].hash
:= 0; Inc(eidx
); end;
699 // mFreeEntryHead will be fixed in `rehash()`
702 // as seed was changed, recalc hash
703 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
707 swpe
:= allocEntry();
712 putEntryInternal(swpe
);
716 // see http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
717 function THashBase
.del (constref akey
: KeyT
): Boolean;
719 khash
, idx
, idxnext
, pdist
, dist
: LongWord;
723 if (mBucketsUsed
= 0) then exit
;
725 bhigh
:= High(mBuckets
);
726 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
727 idx
:= khash
and bhigh
;
730 if (mBuckets
[idx
] = nil) then exit
; // no key
731 for dist
:= 0 to bhigh
do
733 if (mBuckets
[idx
] = nil) then break
;
734 //pdist := distToStIdx(idxcur);
735 pdist
:= mBuckets
[idx
].hash
and bhigh
;
736 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
738 if (dist
> pdist
) then break
;
739 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
740 if result
then break
;
741 idx
:= (idx
+1) and bhigh
;
747 {$IFDEF RBHASH_DEBUG_DELETE}
748 writeln('del: key ', akey
, ': not found');
753 {$IFDEF RBHASH_DEBUG_DELETE}
754 writeln('del: key ', akey
, ': found at ', idx
, '; ek=', mBuckets
[idx
].key
, '; ev=', mBuckets
[idx
].value
);
756 releaseEntry(mBuckets
[idx
]);
758 idxnext
:= (idx
+1) and bhigh
;
759 for dist
:= 0 to bhigh
do
761 {$IFDEF RBHASH_DEBUG_DELETE}
762 writeln(' dist=', dist
, '; idx=', idx
, '; idxnext=', idxnext
, '; ce=', (mBuckets
[idx
] <> nil), '; ne=', (mBuckets
[idxnext
] <> nil));
764 if (mBuckets
[idxnext
] = nil) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' idxnext nil');{$ENDIF} mBuckets
[idx
] := nil; break
; end;
765 //pdist := distToStIdx(idxnext);
766 pdist
:= mBuckets
[idxnext
].hash
and bhigh
;
767 if (pdist
<= idxnext
) then pdist
:= idxnext
-pdist
else pdist
:= idxnext
+((bhigh
+1)-pdist
);
769 if (pdist
= 0) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist is zero');{$ENDIF} mBuckets
[idx
] := nil; break
; end;
770 {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist=', pdist
);{$ENDIF}
771 mBuckets
[idx
] := mBuckets
[idxnext
];
772 idx
:= (idx
+1) and bhigh
;
773 idxnext
:= (idxnext
+1) and bhigh
;
780 procedure THashBase
.rehash ();
784 e
: PEntry
= nil; // shut up, fpc!
785 {$IFDEF RBHASH_SANITY_CHECKS}
789 // change seed, to minimize pathological cases
790 if (mSeed
= 0) then mSeed
:= $29a;
791 mSeed
:= u32Hash(mSeed
);
793 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
796 mFreeEntryHead
:= nil;
798 for idx
:= 0 to High(mEntries
) do
801 if (e
.hash
<> 0) then
803 {$IFDEF RBHASH_SANITY_CHECKS}
804 if (e
.nextFree
<> nil) then raise Exception
.Create('internal error in rehash: inconsistent');
805 if (cnt
= 0) and (idx
<> mFirstEntry
) then raise Exception
.Create('internal error in rehash: inconsistent (1)');
807 if (cnt
= mBucketsUsed
) and (idx
<> mLastEntry
) then raise Exception
.Create('internal error in rehash: inconsistent (2)');
809 e
.hash
:= hashfn(e
.key
) xor mSeed
; if (e
.hash
= 0) then e
.hash
:= $29a;
814 if (lastfree
<> nil) then lastfree
.nextFree
:= e
else mFreeEntryHead
:= e
;
818 if (lastfree
<> nil) then e
.nextFree
:= nil;
819 {$IFDEF RBHASH_SANITY_CHECKS}
820 if (cnt
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table resize (invalid first/last range; 0)');
821 if (cnt
<> mEntriesUsed
) then raise Exception
.Create('internal error in hash table resize (invalid first/last range; 1)');
826 procedure THashBase
.compact ();
828 newsz
, didx
, f
: Integer;
829 {$IFDEF RBHASH_SANITY_CHECKS}
833 newsz
:= nextPOT(LongWord(mBucketsUsed
));
834 if (newsz
>= 1024*1024*1024) then exit
;
835 if (newsz
*2 >= Length(mBuckets
)) then exit
;
836 if (newsz
*2 < 128) then exit
;
837 {$IFDEF RBHASH_DEBUG_COMPACT}writeln('compacting; used=', mBucketsUsed
, '; oldsizePOT=', newsz
, '; newsize=', newsz
*2);{$ENDIF}
839 // move all entries to top
840 if (mFirstEntry
>= 0) then
842 {$IFDEF RBHASH_SANITY_CHECKS}
843 if (mBucketsUsed
< 1) then raise Exception
.Create('internal error in hash table (invalid bucket count; 0)');
846 while (didx
< Length(mEntries
)) do if (mEntries
[didx
].hash
<> 0) then Inc(didx
) else break
;
851 if (mEntries
[f
].hash
<> 0) then
853 {$IFDEF RBHASH_SANITY_CHECKS}
854 if (didx
>= f
) then raise Exception
.Create('internal error in hash: inconsistent');
856 mEntries
[didx
] := mEntries
[f
];
857 mEntries
[f
].hash
:= 0;
859 if (f
= mLastEntry
) then break
;
860 while (didx
< Length(mEntries
)) do if (mEntries
[didx
].hash
<> 0) then Inc(didx
) else break
;
864 {$IFDEF RBHASH_SANITY_CHECKS}
865 if (didx
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 1)');
868 mLastEntry
:= mBucketsUsed
-1;
869 {$IFDEF RBHASH_SANITY_CHECKS}
871 for f
:= mFirstEntry
to mLastEntry
do
873 if (mEntries
[f
].hash
= 0) then raise Exception
.Create('internal error in hash table (invalid first/last range; 2)');
876 if (cnt
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 3)');
877 if (cnt
<> mEntriesUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 4)');
878 for f
:= mLastEntry
+1 to High(mEntries
) do
880 if (mEntries
[f
].hash
<> 0) then raise Exception
.Create('internal error in hash table (invalid first/last range; 5)');
886 {$IFDEF RBHASH_SANITY_CHECKS}
887 if (mBucketsUsed
<> 0) then raise Exception
.Create('internal error in hash table (invalid bucket count; 1)');
891 SetLength(mBuckets
, newsz
);
892 SetLength(mEntries
, newsz
);
893 // mFreeEntryHead will be fixed in `rehash()`
899 function THashBase
.forEach (it
: TIteratorFn
): Boolean;
904 if not assigned(it
) then exit
;
906 if (i
< 0) then exit
;
907 while (i
<= mLastEntry
) do
909 if (mEntries
[i
].hash
<> 0) then
911 result
:= it(mEntries
[i
].key
, mEntries
[i
].value
);
920 function THashBase
.GetEnumerator (): TValEnumerator
;
922 if (Length(mEntries
) > 0) then result
:= TValEnumerator
.Create(@mEntries
[0], mFirstEntry
, mLastEntry
)
923 else result
:= TValEnumerator
.Create(nil, -1, -1);
926 function THashBase
.byKey (): TKeyEnumerator
;
928 if (Length(mEntries
) > 0) then result
:= TKeyEnumerator
.Create(@mEntries
[0], mFirstEntry
, mLastEntry
)
929 else result
:= TKeyEnumerator
.Create(nil, -1, -1);
932 function THashBase
.byValue (): TValEnumerator
;
934 if (Length(mEntries
) > 0) then result
:= TValEnumerator
.Create(@mEntries
[0], mFirstEntry
, mLastEntry
)
935 else result
:= TValEnumerator
.Create(nil, -1, -1);
938 function THashBase
.byKeyValue (): TKeyValEnumerator
; // PEntry
940 if (Length(mEntries
) > 0) then result
:= TKeyValEnumerator
.Create(@mEntries
[0], mFirstEntry
, mLastEntry
)
941 else result
:= TKeyValEnumerator
.Create(nil, -1, -1);
945 // ////////////////////////////////////////////////////////////////////////// //
946 constructor THashBase
.TValEnumerator
.Create (aents
: PEntry
; afirst
, alast
: Integer);
949 mFirstEntry
:= afirst
;
951 cur
:= mFirstEntry
-1;
954 function THashBase
.TValEnumerator
.MoveNext
: Boolean;
957 while (cur
<= mLastEntry
) do
959 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
964 function THashBase
.TValEnumerator
.getCurrent (): ValueT
;
966 result
:= mEntries
[cur
].value
;
970 // ////////////////////////////////////////////////////////////////////////// //
971 constructor THashBase
.TKeyEnumerator
.Create (aents
: PEntry
; afirst
, alast
: Integer);
974 mFirstEntry
:= afirst
;
976 cur
:= mFirstEntry
-1;
979 function THashBase
.TKeyEnumerator
.MoveNext
: Boolean;
982 while (cur
<= mLastEntry
) do
984 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
989 function THashBase
.TKeyEnumerator
.getCurrent (): KeyT
;
991 result
:= mEntries
[cur
].key
;
995 // ////////////////////////////////////////////////////////////////////////// //
996 constructor THashBase
.TKeyValEnumerator
.Create (aents
: PEntry
; afirst
, alast
: Integer);
999 mFirstEntry
:= afirst
;
1000 mLastEntry
:= alast
;
1001 cur
:= mFirstEntry
-1;
1004 function THashBase
.TKeyValEnumerator
.MoveNext
: Boolean;
1007 while (cur
<= mLastEntry
) do
1009 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
1014 function THashBase
.TKeyValEnumerator
.getCurrent (): PEntry
;
1016 result
:= mEntries
+cur
;