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
48 hash
: LongWord; // key hash or 0
49 nextFree
: PEntry
; // next free entry
55 mBuckets
: array of PEntry
; // entries, points to mEntries elements
56 mBucketsUsed
: Integer;
57 mEntries
: array of TEntry
;
58 {$IFDEF RBHASH_SANITY_CHECKS}
59 mEntriesUsed
: Integer;
61 mFreeEntryHead
: PEntry
;
62 mFirstEntry
, mLastEntry
: Integer;
66 function allocEntry (): PEntry
;
67 procedure releaseEntry (e
: PEntry
);
69 //function distToStIdx (idx: LongWord): LongWord; inline;
71 procedure putEntryInternal (swpe
: PEntry
);
73 function getCapacity (): Integer; inline;
76 constructor Create (ahashfn
: THashFn
; aequfn
: TEquFn
);
77 destructor Destroy (); override;
82 procedure compact (); // call this instead of `rehash()` after alot of deletions
84 function get (constref akey
: KeyT
; out rval
: ValueT
): Boolean; // `true`: found
85 function put (constref akey
: KeyT
; constref aval
: ValueT
): Boolean; // `true`: replaced
86 function has (constref akey
: KeyT
): Boolean; // `true`: found
87 function del (constref akey
: KeyT
): Boolean; // `true`: deleted
89 //WARNING! don't modify table in iterator (queries are ok, though)
90 function forEach (it
: TIteratorFn
): Boolean;
92 property count
: Integer read mBucketsUsed
;
93 property capacity
: Integer read getCapacity
;
100 seed
: LongWord; // initial seed value; MUST BE FIRST
101 hash
: LongWord; // current value
104 constructor Create (aseed
: LongWord);
106 procedure reset (); inline; overload
;
107 procedure reset (aseed
: LongWord); inline; overload
;
109 procedure put (const buf
; len
: LongWord);
111 // current hash value
112 // you can continue putting data, as this is not destructive
113 function value
: LongWord; inline;
118 THashIntInt
= specialize THashBase
<Integer, Integer>;
120 function hashNewIntInt (): THashIntInt
;
123 function u32Hash (a
: LongWord): LongWord; inline;
124 function fnvHash (const buf
; len
: LongWord): LongWord;
125 function joaatHash (const buf
; len
: LongWord): LongWord;
127 function nextPOT (x
: LongWord): LongWord; inline;
136 // ////////////////////////////////////////////////////////////////////////// //
139 function nextPOT (x
: LongWord): LongWord; inline;
142 result
:= result
or (result
shr 1);
143 result
:= result
or (result
shr 2);
144 result
:= result
or (result
shr 4);
145 result
:= result
or (result
shr 8);
146 result
:= result
or (result
shr 16);
148 if (x
<> 0) and ((x
and (x
-1)) = 0) then result
:= result
and (not (result
shr 1)) else result
+= 1;
153 // ////////////////////////////////////////////////////////////////////////// //
154 function hiiequ (constref a
, b
: Integer): Boolean; begin result
:= (a
= b
); end;
158 function hiihash (constref k
: Integer): LongWord;
161 result
-= (result
shl 6);
162 result
:= result
xor (result
shr 17);
163 result
-= (result
shl 9);
164 result
:= result
xor (result
shl 4);
165 result
-= (result
shl 3);
166 result
:= result
xor (result
shl 10);
167 result
:= result
xor (result
shr 15);
172 function hashNewIntInt (): THashIntInt
;
174 result
:= THashIntInt
.Create(hiihash
, hiiequ
);
178 // ////////////////////////////////////////////////////////////////////////// //
181 constructor TJoaatHasher
.Create (aseed
: LongWord);
187 procedure TJoaatHasher
.reset (); inline; overload
;
193 procedure TJoaatHasher
.reset (aseed
: LongWord); inline; overload
;
200 procedure TJoaatHasher
.put (const buf
; len
: LongWord);
205 if (len
< 1) then exit
;
206 bytes
:= PByte(@buf
);
212 h
:= h
xor (h
shr 6);
220 function TJoaatHasher
.value
: LongWord; inline;
223 result
+= (result
shl 3);
224 result
:= result
xor (result
shr 11);
225 result
+= (result
shl 15);
230 function joaatHash (const buf
; len
: LongWord): LongWord;
234 h
:= TJoaatHasher
.Create(0);
240 // ////////////////////////////////////////////////////////////////////////// //
243 // fnv-1a: http://www.isthe.com/chongo/tech/comp/fnv/
244 function fnvHash (const buf
; len
: LongWord): LongWord;
249 result
:= 2166136261; // fnv offset basis
252 result
:= result
xor b
^;
253 result
:= result
*16777619; // 32-bit fnv prime
263 function u32Hash (a
: LongWord): LongWord; inline;
266 result
-= (result
shl 6);
267 result
:= result
xor (result
shr 17);
268 result
-= (result
shl 9);
269 result
:= result
xor (result
shl 4);
270 result
-= (result
shl 3);
271 result
:= result
xor (result
shl 10);
272 result
:= result
xor (result
shr 15);
277 // ////////////////////////////////////////////////////////////////////////// //
278 constructor THashBase
.Create (ahashfn
: THashFn
; aequfn
: TEquFn
);
280 if not assigned(ahashfn
) then raise Exception
.Create('cannot create hash without hash function');
281 if not assigned(aequfn
) then raise Exception
.Create('cannot create hash without equality function');
285 mSeed
:= u32Hash($29a);
291 destructor THashBase
.Destroy ();
299 procedure THashBase
.clear ();
303 SetLength(mBuckets
, InitSize
);
304 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
306 SetLength(mEntries
, Length(mBuckets
));
307 for idx
:= 0 to High(mEntries
)-1 do
309 mEntries
[idx
].hash
:= 0;
310 mEntries
[idx
].nextFree
:= @mEntries
[idx
+1]; //idx+1;
312 mEntries
[High(mEntries
)].hash
:= 0;
313 mEntries
[High(mEntries
)].nextFree
:= nil;
316 {$IFDEF RBHASH_SANITY_CHECKS}
319 mFreeEntryHead
:= @mEntries
[0];
325 function THashBase
.getCapacity (): Integer; inline; begin result
:= Length(mBuckets
); end;
328 function THashBase
.allocEntry (): PEntry
;
332 {$IFDEF RBHASH_SANITY_CHECKS}
333 if (mFreeEntryHead
= nil) then raise Exception
.Create('internal error in hash entry allocator (0)');
334 if (mFreeEntryHead
.hash
<> 0) then raise Exception
.Create('internal error in hash entry allocator (1)');
336 result
:= mFreeEntryHead
;
337 mFreeEntryHead
:= result
.nextFree
;
338 {$IFDEF RBHASH_SANITY_CHECKS}
341 result
.nextFree
:= nil; // just in case
342 // fix mFirstEntry and mLastEntry
343 idx
:= Integer((PtrUInt(result
)-PtrUInt(@mEntries
[0])) div sizeof(mEntries
[0]));
344 {$IFDEF RBHASH_SANITY_CHECKS}
345 if (idx
< 0) or (idx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid entry address)');
346 if (result
<> @mEntries
[idx
]) then raise Exception
.Create('internal error in hash entry allocator (wtf?!)');
348 if (mFirstEntry
< 0) or (idx
< mFirstEntry
) then mFirstEntry
:= idx
;
349 if (idx
> mLastEntry
) then mLastEntry
:= idx
;
353 procedure THashBase
.releaseEntry (e
: PEntry
);
357 {$IFDEF RBHASH_SANITY_CHECKS}
358 if (mEntriesUsed
= 0) then raise Exception
.Create('internal error in hash entry allocator');
359 if (mEntriesUsed
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash entry allocator (entry/bucket count mismatch)');
360 if (e
= nil) then raise Exception
.Create('internal error in hash entry allocator (trying to release nil entry)');
361 if (e
.hash
= 0) then raise Exception
.Create('internal error in hash entry allocator (trying to release unallocated entry)');
363 idx
:= Integer((PtrUInt(e
)-PtrUInt(@mEntries
[0])) div sizeof(mEntries
[0]));
364 {$IFDEF RBHASH_SANITY_CHECKS}
365 if (idx
< 0) or (idx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid entry address)');
366 if (e
<> @mEntries
[idx
]) then raise Exception
.Create('internal error in hash entry allocator (wtf?!)');
369 e
.nextFree
:= mFreeEntryHead
;
370 mFreeEntryHead
:= e
; //idx;
371 {$IFDEF RBHASH_SANITY_CHECKS}
374 // fix mFirstEntry and mLastEntry
375 {$IFDEF RBHASH_SANITY_CHECKS}
376 if (mFirstEntry
< 0) or (mLastEntry
< 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 0)');
378 if (mFirstEntry
= mLastEntry
) then
380 {$IFDEF RBHASH_SANITY_CHECKS}
381 if (mEntriesUsed
<> 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 1)');
388 {$IFDEF RBHASH_SANITY_CHECKS}
389 if (mEntriesUsed
= 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 2)');
391 // fix first entry index
392 if (idx
= mFirstEntry
) then
395 while (mEntries
[cidx
].hash
= 0) do Inc(cidx
);
396 {$IFDEF RBHASH_SANITY_CHECKS}
397 if (cidx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 3)');
401 // fix last entry index
402 if (idx
= mLastEntry
) then
405 while (mEntries
[cidx
].hash
= 0) do Dec(cidx
);
406 {$IFDEF RBHASH_SANITY_CHECKS}
407 if (cidx
< 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 3)');
416 function THashBase.distToStIdx (idx: LongWord): LongWord; inline;
418 {$IFDEF RBHASH_SANITY_CHECKS}
419 assert(idx < Length(mBuckets));
420 assert(mBuckets[idx] <> nil);
422 result := mBuckets[idx].hash and High(mBuckets);
423 if (result <= idx) then result := idx-result else result := idx+(Length(mBuckets)-result);
428 function THashBase
.has (constref akey
: KeyT
): Boolean;
430 khash
, idx
: LongWord;
431 dist
, pdist
: LongWord;
435 if (mBucketsUsed
= 0) then exit
;
437 bhigh
:= High(mBuckets
);
438 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
439 idx
:= khash
and bhigh
;
440 if (mBuckets
[idx
] = nil) then exit
;
442 for dist
:= 0 to bhigh
do
444 if (mBuckets
[idx
] = nil) then break
;
445 //pdist := distToStIdx(idx);
446 pdist
:= mBuckets
[idx
].hash
and bhigh
;
447 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
449 if (dist
> pdist
) then break
;
450 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
451 if result
then break
;
452 idx
:= (idx
+1) and bhigh
;
457 function THashBase
.get (constref akey
: KeyT
; out rval
: ValueT
): Boolean;
459 khash
, idx
: LongWord;
460 dist
, pdist
: LongWord;
464 if (mBucketsUsed
= 0) then begin rval
:= Default(ValueT
); exit
; end;
466 bhigh
:= High(mBuckets
);
467 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
468 idx
:= khash
and bhigh
;
469 if (mBuckets
[idx
] = nil) then begin rval
:= Default(ValueT
); exit
; end;
471 for dist
:= 0 to bhigh
do
473 if (mBuckets
[idx
] = nil) then break
;
474 //pdist := distToStIdx(idx);
475 pdist
:= mBuckets
[idx
].hash
and bhigh
;
476 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
478 if (dist
> pdist
) then break
;
479 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
482 rval
:= mBuckets
[idx
].value
;
485 idx
:= (idx
+1) and bhigh
;
488 if not result
then rval
:= Default(ValueT
); // just in case
492 procedure THashBase
.putEntryInternal (swpe
: PEntry
);
494 idx
, dist
, pcur
, pdist
: LongWord;
495 tmpe
: PEntry
; // current entry to swap (or nothing)
498 bhigh
:= High(mBuckets
);
499 idx
:= swpe
.hash
and bhigh
;
500 {$IFDEF RBHASH_DEBUG_INSERT}writeln('inserting key ', swpe
.key
, '; value=', swpe
.value
, '; wantidx=', idx
, '; bhigh=', bhigh
);{$ENDIF}
502 for dist
:= 0 to bhigh
do
504 if (mBuckets
[idx
] = nil) then
507 {$IFDEF RBHASH_DEBUG_INSERT}writeln(' inserted to ', idx
);{$ENDIF}
508 mBuckets
[idx
] := swpe
;
512 //pdist := distToStIdx(idx);
513 pdist
:= mBuckets
[idx
].hash
and bhigh
;
514 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
516 if (pcur
> pdist
) then
518 // swapping the current bucket with the one to insert
519 tmpe
:= mBuckets
[idx
];
520 mBuckets
[idx
] := swpe
;
524 idx
:= (idx
+1) and bhigh
;
530 function THashBase
.put (constref akey
: KeyT
; constref aval
: ValueT
): Boolean;
532 khash
, idx
, dist
, pdist
: LongWord;
533 swpe
: PEntry
= nil; // current entry to swap (or nothing)
535 newsz
, eidx
: Integer;
539 bhigh
:= High(mBuckets
);
540 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
541 idx
:= khash
and bhigh
;
543 // check if we already have this key
544 if (mBucketsUsed
<> 0) and (mBuckets
[idx
] <> nil) then
546 for dist
:= 0 to bhigh
do
548 if (mBuckets
[idx
] = nil) then break
;
549 //pdist := distToStIdx(idx);
550 pdist
:= mBuckets
[idx
].hash
and bhigh
;
551 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
553 if (dist
> pdist
) then break
;
554 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
558 //mBuckets[idx].key := akey;
559 mBuckets
[idx
].value
:= aval
;
562 idx
:= (idx
+1) and bhigh
;
566 // need to resize hash?
567 if (mBucketsUsed
>= (bhigh
+1)*LoadFactorPrc
div 100) then
569 newsz
:= Length(mBuckets
);
570 if (Length(mEntries
) <> newsz
) then raise Exception
.Create('internal error in hash table (resize)');
571 if (newsz
<= 1024*1024*1024) then newsz
*= 2 else raise Exception
.Create('hash table too big');
572 {$IFDEF RBHASH_DEBUG_RESIZE}
573 writeln('resizing hash; used=', mBucketsUsed
, '; total=', (bhigh
+1), '; maxload=', (bhigh
+1)*LoadFactorPrc
div 100, '; newsz=', newsz
);
575 SetLength(mBuckets
, newsz
);
576 // resize entries array
577 eidx
:= Length(mEntries
);
578 SetLength(mEntries
, newsz
);
579 while (eidx
< Length(mEntries
)) do begin mEntries
[eidx
].hash
:= 0; Inc(eidx
); end;
580 // mFreeEntryHead will be fixed in `rehash()`
583 // as seed was changed, recalc hash
584 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
588 swpe
:= allocEntry();
593 putEntryInternal(swpe
);
597 // see http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
598 function THashBase
.del (constref akey
: KeyT
): Boolean;
600 khash
, idx
, idxnext
, pdist
, dist
: LongWord;
604 if (mBucketsUsed
= 0) then exit
;
606 bhigh
:= High(mBuckets
);
607 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
608 idx
:= khash
and bhigh
;
611 if (mBuckets
[idx
] = nil) then exit
; // no key
612 for dist
:= 0 to bhigh
do
614 if (mBuckets
[idx
] = nil) then break
;
615 //pdist := distToStIdx(idxcur);
616 pdist
:= mBuckets
[idx
].hash
and bhigh
;
617 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
619 if (dist
> pdist
) then break
;
620 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
621 if result
then break
;
622 idx
:= (idx
+1) and bhigh
;
628 {$IFDEF RBHASH_DEBUG_DELETE}
629 writeln('del: key ', akey
, ': not found');
634 {$IFDEF RBHASH_DEBUG_DELETE}
635 writeln('del: key ', akey
, ': found at ', idx
, '; ek=', mBuckets
[idx
].key
, '; ev=', mBuckets
[idx
].value
);
637 releaseEntry(mBuckets
[idx
]);
639 idxnext
:= (idx
+1) and bhigh
;
640 for dist
:= 0 to bhigh
do
642 {$IFDEF RBHASH_DEBUG_DELETE}
643 writeln(' dist=', dist
, '; idx=', idx
, '; idxnext=', idxnext
, '; ce=', (mBuckets
[idx
] <> nil), '; ne=', (mBuckets
[idxnext
] <> nil));
645 if (mBuckets
[idxnext
] = nil) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' idxnext nil');{$ENDIF} mBuckets
[idx
] := nil; break
; end;
646 //pdist := distToStIdx(idxnext);
647 pdist
:= mBuckets
[idxnext
].hash
and bhigh
;
648 if (pdist
<= idxnext
) then pdist
:= idxnext
-pdist
else pdist
:= idxnext
+((bhigh
+1)-pdist
);
650 if (pdist
= 0) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist is zero');{$ENDIF} mBuckets
[idx
] := nil; break
; end;
651 {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist=', pdist
);{$ENDIF}
652 mBuckets
[idx
] := mBuckets
[idxnext
];
653 idx
:= (idx
+1) and bhigh
;
654 idxnext
:= (idxnext
+1) and bhigh
;
661 procedure THashBase
.rehash ();
665 e
: PEntry
= nil; // shut up, fpc!
666 {$IFDEF RBHASH_SANITY_CHECKS}
670 // change seed, to minimize pathological cases
671 if (mSeed
= 0) then mSeed
:= $29a;
672 mSeed
:= u32Hash(mSeed
);
674 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
677 mFreeEntryHead
:= nil;
679 for idx
:= 0 to High(mEntries
) do
682 if (e
.hash
<> 0) then
684 {$IFDEF RBHASH_SANITY_CHECKS}
685 if (e
.nextFree
<> nil) then raise Exception
.Create('internal error in rehash: inconsistent');
686 if (cnt
= 0) and (idx
<> mFirstEntry
) then raise Exception
.Create('internal error in rehash: inconsistent (1)');
688 if (cnt
= mBucketsUsed
) and (idx
<> mLastEntry
) then raise Exception
.Create('internal error in rehash: inconsistent (2)');
690 e
.hash
:= hashfn(e
.key
) xor mSeed
; if (e
.hash
= 0) then e
.hash
:= $29a;
695 if (lastfree
<> nil) then lastfree
.nextFree
:= e
else mFreeEntryHead
:= e
;
699 if (lastfree
<> nil) then e
.nextFree
:= nil;
700 {$IFDEF RBHASH_SANITY_CHECKS}
701 if (cnt
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table resize (invalid first/last range; 0)');
702 if (cnt
<> mEntriesUsed
) then raise Exception
.Create('internal error in hash table resize (invalid first/last range; 1)');
707 procedure THashBase
.compact ();
709 newsz
, didx
, f
: Integer;
710 {$IFDEF RBHASH_SANITY_CHECKS}
714 newsz
:= nextPOT(LongWord(mBucketsUsed
));
715 if (newsz
>= 1024*1024*1024) then exit
;
716 if (newsz
*2 >= Length(mBuckets
)) then exit
;
717 if (newsz
*2 < 128) then exit
;
718 {$IFDEF RBHASH_DEBUG_COMPACT}writeln('compacting; used=', mBucketsUsed
, '; oldsizePOT=', newsz
, '; newsize=', newsz
*2);{$ENDIF}
720 // move all entries to top
721 if (mFirstEntry
>= 0) then
723 {$IFDEF RBHASH_SANITY_CHECKS}
724 if (mBucketsUsed
< 1) then raise Exception
.Create('internal error in hash table (invalid bucket count; 0)');
727 while (didx
< Length(mEntries
)) do if (mEntries
[didx
].hash
<> 0) then Inc(didx
) else break
;
732 if (mEntries
[f
].hash
<> 0) then
734 {$IFDEF RBHASH_SANITY_CHECKS}
735 if (didx
>= f
) then raise Exception
.Create('internal error in hash: inconsistent');
737 mEntries
[didx
] := mEntries
[f
];
738 mEntries
[f
].hash
:= 0;
740 if (f
= mLastEntry
) then break
;
741 while (didx
< Length(mEntries
)) do if (mEntries
[didx
].hash
<> 0) then Inc(didx
) else break
;
745 {$IFDEF RBHASH_SANITY_CHECKS}
746 if (didx
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 1)');
749 mLastEntry
:= mBucketsUsed
-1;
750 {$IFDEF RBHASH_SANITY_CHECKS}
752 for f
:= mFirstEntry
to mLastEntry
do
754 if (mEntries
[f
].hash
= 0) then raise Exception
.Create('internal error in hash table (invalid first/last range; 2)');
757 if (cnt
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 3)');
758 if (cnt
<> mEntriesUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 4)');
759 for f
:= mLastEntry
+1 to High(mEntries
) do
761 if (mEntries
[f
].hash
<> 0) then raise Exception
.Create('internal error in hash table (invalid first/last range; 5)');
767 {$IFDEF RBHASH_SANITY_CHECKS}
768 if (mBucketsUsed
<> 0) then raise Exception
.Create('internal error in hash table (invalid bucket count; 1)');
772 SetLength(mBuckets
, newsz
);
773 SetLength(mEntries
, newsz
);
774 // mFreeEntryHead will be fixed in `rehash()`
780 function THashBase
.forEach (it
: TIteratorFn
): Boolean;
785 if not assigned(it
) then exit
;
787 if (i
< 0) then exit
;
788 while (i
<= mLastEntry
) do
790 if (mEntries
[i
].hash
<> 0) then
792 result
:= it(mEntries
[i
].key
, mEntries
[i
].value
);