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>;
164 THashStrStr
= specialize THashBase
<AnsiString, AnsiString>;
166 function hashNewIntInt (): THashIntInt
;
167 function hashNewStrInt (): THashStrInt
;
168 function hashNewStrStr (): THashStrStr
;
171 function u32Hash (a
: LongWord): LongWord; inline;
172 function fnvHash (constref buf
; len
: LongWord): LongWord;
173 function joaatHash (constref buf
; len
: LongWord): LongWord;
175 function nextPOT (x
: LongWord): LongWord; inline;
179 function hiiequ (constref a
, b
: Integer): Boolean;
180 function hiihash (constref k
: Integer): LongWord;
181 function hsiequ (constref a
, b
: AnsiString): Boolean;
182 function hsihash (constref k
: AnsiString): LongWord;
191 // ////////////////////////////////////////////////////////////////////////// //
194 function nextPOT (x
: LongWord): LongWord; inline;
197 result
:= result
or (result
shr 1);
198 result
:= result
or (result
shr 2);
199 result
:= result
or (result
shr 4);
200 result
:= result
or (result
shr 8);
201 result
:= result
or (result
shr 16);
203 if (x
<> 0) and ((x
and (x
-1)) = 0) then result
:= result
and (not (result
shr 1)) else result
+= 1;
208 // ////////////////////////////////////////////////////////////////////////// //
209 function hiiequ (constref a
, b
: Integer): Boolean; begin result
:= (a
= b
); end;
210 function hsiequ (constref a
, b
: AnsiString): Boolean; begin result
:= (a
= b
); end;
214 function hiihash (constref k
: Integer): LongWord;
216 result
:= LongWord(k
);
217 result
-= (result
shl 6);
218 result
:= result
xor (result
shr 17);
219 result
-= (result
shl 9);
220 result
:= result
xor (result
shl 4);
221 result
-= (result
shl 3);
222 result
:= result
xor (result
shl 10);
223 result
:= result
xor (result
shr 15);
226 function hsihash (constref k
: AnsiString): LongWord;
228 if (Length(k
) > 0) then result
:= fnvHash(PAnsiChar(k
)^, Length(k
)) else result
:= 0;
233 function hashNewIntInt (): THashIntInt
;
235 result
:= THashIntInt
.Create(hiihash
, hiiequ
);
239 function hashNewStrInt (): THashStrInt
;
241 result
:= THashStrInt
.Create(hsihash
, hsiequ
);
245 function hashNewStrStr (): THashStrStr
;
247 result
:= THashStrStr
.Create(hsihash
, hsiequ
);
251 // ////////////////////////////////////////////////////////////////////////// //
254 constructor TJoaatHasher
.Create (aseed
: LongWord);
260 procedure TJoaatHasher
.reset (); inline; overload
;
266 procedure TJoaatHasher
.reset (aseed
: LongWord); inline; overload
;
273 procedure TJoaatHasher
.put (constref buf
; len
: LongWord);
278 if (len
< 1) then exit
;
279 bytes
:= PByte(@buf
);
285 h
:= h
xor (h
shr 6);
293 function TJoaatHasher
.value
: LongWord; inline;
296 result
+= (result
shl 3);
297 result
:= result
xor (result
shr 11);
298 result
+= (result
shl 15);
303 function joaatHash (constref buf
; len
: LongWord): LongWord;
307 h
:= TJoaatHasher
.Create(0);
308 h
.put(PByte(@buf
)^, len
);
313 // ////////////////////////////////////////////////////////////////////////// //
316 // fnv-1a: http://www.isthe.com/chongo/tech/comp/fnv/
317 function fnvHash (constref buf
; len
: LongWord): LongWord;
322 result
:= 2166136261; // fnv offset basis
325 result
:= result
xor b
^;
326 result
:= result
*16777619; // 32-bit fnv prime
336 function u32Hash (a
: LongWord): LongWord; inline;
339 result
-= (result
shl 6);
340 result
:= result
xor (result
shr 17);
341 result
-= (result
shl 9);
342 result
:= result
xor (result
shl 4);
343 result
-= (result
shl 3);
344 result
:= result
xor (result
shl 10);
345 result
:= result
xor (result
shr 15);
350 // ////////////////////////////////////////////////////////////////////////// //
351 constructor THashBase
.Create (ahashfn
: THashFn
; aequfn
: TEquFn
);
353 if not assigned(ahashfn
) then raise Exception
.Create('cannot create hash without hash function');
354 if not assigned(aequfn
) then raise Exception
.Create('cannot create hash without equality function');
358 mSeed
:= u32Hash($29a);
364 destructor THashBase
.Destroy ();
372 procedure THashBase
.clear ();
376 SetLength(mBuckets
, InitSize
);
377 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
379 SetLength(mEntries
, Length(mBuckets
));
381 for idx := 0 to High(mEntries)-1 do
383 mEntries[idx].hash := 0;
384 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
386 mEntries[High(mEntries)].hash := 0;
387 mEntries[High(mEntries)].nextFree := nil;
390 for idx := 0 to High(mEntries) do
392 mEntries[idx].hash := 0;
393 mEntries[idx].nextFree := nil;
398 {$IFDEF RBHASH_SANITY_CHECKS}
401 mFreeEntryHead
:= nil; //@mEntries[0];
407 procedure THashBase
.reset ();
411 if (mBucketsUsed
> 0) then
413 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
415 for idx := 0 to High(mEntries)-1 do
417 mEntries[idx].hash := 0;
418 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
420 mEntries[High(mEntries)].hash := 0;
421 mEntries[High(mEntries)].nextFree := nil;
424 if (mFirstEntry >= 0) then
426 for idx := mFirstEntry to mLastEntry do
428 mEntries[idx].hash := 0;
429 mEntries[idx].nextFree := nil;
435 {$IFDEF RBHASH_SANITY_CHECKS}
438 mFreeEntryHead
:= nil; //@mEntries[0];
445 function THashBase
.getCapacity (): Integer; inline; begin result
:= Length(mBuckets
); end;
448 function THashBase
.allocEntry (): PEntry
;
452 if (mFreeEntryHead
= nil) then
454 if (mLastEntry
= High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (0.0)');
456 if (mFirstEntry
= -1) then
458 if (mLastEntry
<> 0) then raise Exception
.Create('internal error in hash entry allocator (0.1)');
461 result
:= @mEntries
[mLastEntry
];
462 result
.nextFree
:= nil; // just in case
463 {$IFDEF RBHASH_SANITY_CHECKS}
468 {$IFDEF RBHASH_SANITY_CHECKS}
469 if (mFreeEntryHead
= nil) then raise Exception
.Create('internal error in hash entry allocator (0)');
470 if (mFreeEntryHead
.hash
<> 0) then raise Exception
.Create('internal error in hash entry allocator (1)');
472 result
:= mFreeEntryHead
;
473 mFreeEntryHead
:= result
.nextFree
;
474 {$IFDEF RBHASH_SANITY_CHECKS}
477 result
.nextFree
:= nil; // just in case
478 // fix mFirstEntry and mLastEntry
479 idx
:= Integer((PtrUInt(result
)-PtrUInt(@mEntries
[0])) div sizeof(mEntries
[0]));
480 {$IFDEF RBHASH_SANITY_CHECKS}
481 if (idx
< 0) or (idx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid entry address)');
482 if (result
<> @mEntries
[idx
]) then raise Exception
.Create('internal error in hash entry allocator (wtf?!)');
484 if (mFirstEntry
< 0) or (idx
< mFirstEntry
) then mFirstEntry
:= idx
;
485 if (idx
> mLastEntry
) then mLastEntry
:= idx
;
489 procedure THashBase
.releaseEntry (e
: PEntry
);
493 {$IFDEF RBHASH_SANITY_CHECKS}
494 if (mEntriesUsed
= 0) then raise Exception
.Create('internal error in hash entry allocator');
495 if (mEntriesUsed
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash entry allocator (entry/bucket count mismatch)');
496 if (e
= nil) then raise Exception
.Create('internal error in hash entry allocator (trying to release nil entry)');
497 if (e
.hash
= 0) then raise Exception
.Create('internal error in hash entry allocator (trying to release unallocated entry)');
499 idx
:= Integer((PtrUInt(e
)-PtrUInt(@mEntries
[0])) div sizeof(mEntries
[0]));
500 {$IFDEF RBHASH_SANITY_CHECKS}
501 if (idx
< 0) or (idx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid entry address)');
502 if (e
<> @mEntries
[idx
]) then raise Exception
.Create('internal error in hash entry allocator (wtf?!)');
504 {$IFDEF RBHASH_SANITY_CHECKS}
508 e
.nextFree
:= mFreeEntryHead
;
509 mFreeEntryHead
:= e
; //idx;
510 // fix mFirstEntry and mLastEntry
511 {$IFDEF RBHASH_SANITY_CHECKS}
512 if (mFirstEntry
< 0) or (mLastEntry
< 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 0)');
514 if (mFirstEntry
= mLastEntry
) then
516 {$IFDEF RBHASH_SANITY_CHECKS}
517 if (mEntriesUsed
<> 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 1)');
524 {$IFDEF RBHASH_SANITY_CHECKS}
525 if (mEntriesUsed
= 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 2)');
527 // fix first entry index
528 if (idx
= mFirstEntry
) then
531 while (mEntries
[cidx
].hash
= 0) do Inc(cidx
);
532 {$IFDEF RBHASH_SANITY_CHECKS}
533 if (cidx
> High(mEntries
)) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 3)');
537 // fix last entry index
538 if (idx
= mLastEntry
) then
541 while (mEntries
[cidx
].hash
= 0) do Dec(cidx
);
542 {$IFDEF RBHASH_SANITY_CHECKS}
543 if (cidx
< 0) then raise Exception
.Create('internal error in hash entry allocator (invalid first/last range; 3)');
552 function THashBase.distToStIdx (idx: LongWord): LongWord; inline;
554 {$IFDEF RBHASH_SANITY_CHECKS}
555 assert(idx < Length(mBuckets));
556 assert(mBuckets[idx] <> nil);
558 result := mBuckets[idx].hash and High(mBuckets);
559 if (result <= idx) then result := idx-result else result := idx+(Length(mBuckets)-result);
564 function THashBase
.has (constref akey
: KeyT
): Boolean;
566 khash
, idx
: LongWord;
567 dist
, pdist
: LongWord;
571 if (mBucketsUsed
= 0) then exit
;
573 bhigh
:= High(mBuckets
);
574 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
575 idx
:= khash
and bhigh
;
576 if (mBuckets
[idx
] = nil) then exit
;
578 for dist
:= 0 to bhigh
do
580 if (mBuckets
[idx
] = nil) then break
;
581 //pdist := distToStIdx(idx);
582 pdist
:= mBuckets
[idx
].hash
and bhigh
;
583 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
585 if (dist
> pdist
) then break
;
586 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
587 if result
then break
;
588 idx
:= (idx
+1) and bhigh
;
593 function THashBase
.get (constref akey
: KeyT
; out rval
: ValueT
): Boolean;
595 khash
, idx
: LongWord;
596 dist
, pdist
: LongWord;
600 if (mBucketsUsed
= 0) then begin rval
:= Default(ValueT
); exit
; end;
602 bhigh
:= High(mBuckets
);
603 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
604 idx
:= khash
and bhigh
;
605 if (mBuckets
[idx
] = nil) then begin rval
:= Default(ValueT
); exit
; end;
607 for dist
:= 0 to bhigh
do
609 if (mBuckets
[idx
] = nil) then break
;
610 //pdist := distToStIdx(idx);
611 pdist
:= mBuckets
[idx
].hash
and bhigh
;
612 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
614 if (dist
> pdist
) then break
;
615 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
618 rval
:= mBuckets
[idx
].value
;
621 idx
:= (idx
+1) and bhigh
;
624 if not result
then rval
:= Default(ValueT
); // just in case
628 procedure THashBase
.putEntryInternal (swpe
: PEntry
);
630 idx
, dist
, pcur
, pdist
: LongWord;
631 tmpe
: PEntry
; // current entry to swap (or nothing)
634 bhigh
:= High(mBuckets
);
635 idx
:= swpe
.hash
and bhigh
;
636 {$IFDEF RBHASH_DEBUG_INSERT}writeln('inserting key ', swpe
.key
, '; value=', swpe
.value
, '; wantidx=', idx
, '; bhigh=', bhigh
);{$ENDIF}
638 for dist
:= 0 to bhigh
do
640 if (mBuckets
[idx
] = nil) then
643 {$IFDEF RBHASH_DEBUG_INSERT}writeln(' inserted to ', idx
);{$ENDIF}
644 mBuckets
[idx
] := swpe
;
648 //pdist := distToStIdx(idx);
649 pdist
:= mBuckets
[idx
].hash
and bhigh
;
650 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
652 if (pcur
> pdist
) then
654 // swapping the current bucket with the one to insert
655 tmpe
:= mBuckets
[idx
];
656 mBuckets
[idx
] := swpe
;
660 idx
:= (idx
+1) and bhigh
;
666 function THashBase
.put (constref akey
: KeyT
; constref aval
: ValueT
): Boolean;
668 khash
, idx
, dist
, pdist
: LongWord;
669 swpe
: PEntry
= nil; // current entry to swap (or nothing)
671 newsz
, eidx
: Integer;
675 bhigh
:= High(mBuckets
);
676 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
677 idx
:= khash
and bhigh
;
679 // check if we already have this key
680 if (mBucketsUsed
<> 0) and (mBuckets
[idx
] <> nil) then
682 for dist
:= 0 to bhigh
do
684 if (mBuckets
[idx
] = nil) then break
;
685 //pdist := distToStIdx(idx);
686 pdist
:= mBuckets
[idx
].hash
and bhigh
;
687 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
689 if (dist
> pdist
) then break
;
690 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
694 //mBuckets[idx].key := akey;
695 mBuckets
[idx
].value
:= aval
;
698 idx
:= (idx
+1) and bhigh
;
702 // need to resize hash?
703 if (mBucketsUsed
>= (bhigh
+1)*LoadFactorPrc
div 100) then
705 newsz
:= Length(mBuckets
);
706 if (Length(mEntries
) <> newsz
) then raise Exception
.Create('internal error in hash table (resize)');
707 if (newsz
<= 1024*1024*1024) then newsz
*= 2 else raise Exception
.Create('hash table too big');
708 {$IFDEF RBHASH_DEBUG_RESIZE}
709 writeln('resizing hash; used=', mBucketsUsed
, '; total=', (bhigh
+1), '; maxload=', (bhigh
+1)*LoadFactorPrc
div 100, '; newsz=', newsz
);
711 SetLength(mBuckets
, newsz
);
712 // resize entries array
713 eidx
:= Length(mEntries
);
714 SetLength(mEntries
, newsz
);
715 while (eidx
< Length(mEntries
)) do begin mEntries
[eidx
].hash
:= 0; Inc(eidx
); end;
716 // mFreeEntryHead will be fixed in `rehash()`
719 // as seed was changed, recalc hash
720 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
724 swpe
:= allocEntry();
729 putEntryInternal(swpe
);
733 // see http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
734 function THashBase
.del (constref akey
: KeyT
): Boolean;
736 khash
, idx
, idxnext
, pdist
, dist
: LongWord;
740 if (mBucketsUsed
= 0) then exit
;
742 bhigh
:= High(mBuckets
);
743 khash
:= hashfn(akey
) xor mSeed
; if (khash
= 0) then khash
:= $29a;
744 idx
:= khash
and bhigh
;
747 if (mBuckets
[idx
] = nil) then exit
; // no key
748 for dist
:= 0 to bhigh
do
750 if (mBuckets
[idx
] = nil) then break
;
751 //pdist := distToStIdx(idxcur);
752 pdist
:= mBuckets
[idx
].hash
and bhigh
;
753 if (pdist
<= idx
) then pdist
:= idx
-pdist
else pdist
:= idx
+((bhigh
+1)-pdist
);
755 if (dist
> pdist
) then break
;
756 result
:= (mBuckets
[idx
].hash
= khash
) and equfn(mBuckets
[idx
].key
, akey
);
757 if result
then break
;
758 idx
:= (idx
+1) and bhigh
;
764 {$IFDEF RBHASH_DEBUG_DELETE}
765 writeln('del: key ', akey
, ': not found');
770 {$IFDEF RBHASH_DEBUG_DELETE}
771 writeln('del: key ', akey
, ': found at ', idx
, '; ek=', mBuckets
[idx
].key
, '; ev=', mBuckets
[idx
].value
);
773 releaseEntry(mBuckets
[idx
]);
775 idxnext
:= (idx
+1) and bhigh
;
776 for dist
:= 0 to bhigh
do
778 {$IFDEF RBHASH_DEBUG_DELETE}
779 writeln(' dist=', dist
, '; idx=', idx
, '; idxnext=', idxnext
, '; ce=', (mBuckets
[idx
] <> nil), '; ne=', (mBuckets
[idxnext
] <> nil));
781 if (mBuckets
[idxnext
] = nil) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' idxnext nil');{$ENDIF} mBuckets
[idx
] := nil; break
; end;
782 //pdist := distToStIdx(idxnext);
783 pdist
:= mBuckets
[idxnext
].hash
and bhigh
;
784 if (pdist
<= idxnext
) then pdist
:= idxnext
-pdist
else pdist
:= idxnext
+((bhigh
+1)-pdist
);
786 if (pdist
= 0) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist is zero');{$ENDIF} mBuckets
[idx
] := nil; break
; end;
787 {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist=', pdist
);{$ENDIF}
788 mBuckets
[idx
] := mBuckets
[idxnext
];
789 idx
:= (idx
+1) and bhigh
;
790 idxnext
:= (idxnext
+1) and bhigh
;
797 procedure THashBase
.rehash ();
801 e
: PEntry
= nil; // shut up, fpc!
802 {$IFDEF RBHASH_SANITY_CHECKS}
806 // change seed, to minimize pathological cases
807 if (mSeed
= 0) then mSeed
:= $29a;
808 mSeed
:= u32Hash(mSeed
);
810 for idx
:= 0 to High(mBuckets
) do mBuckets
[idx
] := nil;
813 mFreeEntryHead
:= nil;
815 for idx
:= 0 to High(mEntries
) do
818 if (e
.hash
<> 0) then
820 {$IFDEF RBHASH_SANITY_CHECKS}
821 if (e
.nextFree
<> nil) then raise Exception
.Create('internal error in rehash: inconsistent');
822 if (cnt
= 0) and (idx
<> mFirstEntry
) then raise Exception
.Create('internal error in rehash: inconsistent (1)');
824 if (cnt
= mBucketsUsed
) and (idx
<> mLastEntry
) then raise Exception
.Create('internal error in rehash: inconsistent (2)');
826 e
.hash
:= hashfn(e
.key
) xor mSeed
; if (e
.hash
= 0) then e
.hash
:= $29a;
831 if (lastfree
<> nil) then lastfree
.nextFree
:= e
else mFreeEntryHead
:= e
;
835 if (lastfree
<> nil) then e
.nextFree
:= nil;
836 {$IFDEF RBHASH_SANITY_CHECKS}
837 if (cnt
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table resize (invalid first/last range; 0)');
838 if (cnt
<> mEntriesUsed
) then raise Exception
.Create('internal error in hash table resize (invalid first/last range; 1)');
843 procedure THashBase
.compact ();
845 newsz
, didx
, f
: Integer;
846 {$IFDEF RBHASH_SANITY_CHECKS}
850 newsz
:= nextPOT(LongWord(mBucketsUsed
));
851 if (newsz
>= 1024*1024*1024) then exit
;
852 if (newsz
*2 >= Length(mBuckets
)) then exit
;
853 if (newsz
*2 < 128) then exit
;
854 {$IFDEF RBHASH_DEBUG_COMPACT}writeln('compacting; used=', mBucketsUsed
, '; oldsizePOT=', newsz
, '; newsize=', newsz
*2);{$ENDIF}
856 // move all entries to top
857 if (mFirstEntry
>= 0) then
859 {$IFDEF RBHASH_SANITY_CHECKS}
860 if (mBucketsUsed
< 1) then raise Exception
.Create('internal error in hash table (invalid bucket count; 0)');
863 while (didx
< Length(mEntries
)) do if (mEntries
[didx
].hash
<> 0) then Inc(didx
) else break
;
868 if (mEntries
[f
].hash
<> 0) then
870 {$IFDEF RBHASH_SANITY_CHECKS}
871 if (didx
>= f
) then raise Exception
.Create('internal error in hash: inconsistent');
873 mEntries
[didx
] := mEntries
[f
];
874 mEntries
[f
].hash
:= 0;
876 if (f
= mLastEntry
) then break
;
877 while (didx
< Length(mEntries
)) do if (mEntries
[didx
].hash
<> 0) then Inc(didx
) else break
;
881 {$IFDEF RBHASH_SANITY_CHECKS}
882 if (didx
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 1)');
885 mLastEntry
:= mBucketsUsed
-1;
886 {$IFDEF RBHASH_SANITY_CHECKS}
888 for f
:= mFirstEntry
to mLastEntry
do
890 if (mEntries
[f
].hash
= 0) then raise Exception
.Create('internal error in hash table (invalid first/last range; 2)');
893 if (cnt
<> mBucketsUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 3)');
894 if (cnt
<> mEntriesUsed
) then raise Exception
.Create('internal error in hash table (invalid first/last range; 4)');
895 for f
:= mLastEntry
+1 to High(mEntries
) do
897 if (mEntries
[f
].hash
<> 0) then raise Exception
.Create('internal error in hash table (invalid first/last range; 5)');
903 {$IFDEF RBHASH_SANITY_CHECKS}
904 if (mBucketsUsed
<> 0) then raise Exception
.Create('internal error in hash table (invalid bucket count; 1)');
908 SetLength(mBuckets
, newsz
);
909 SetLength(mEntries
, newsz
);
910 // mFreeEntryHead will be fixed in `rehash()`
916 function THashBase
.forEach (it
: TIteratorFn
): Boolean;
921 if not assigned(it
) then exit
;
923 if (i
< 0) then exit
;
924 while (i
<= mLastEntry
) do
926 if (mEntries
[i
].hash
<> 0) then
928 result
:= it(mEntries
[i
].key
, mEntries
[i
].value
);
937 function THashBase
.GetEnumerator (): TValEnumerator
;
939 if (Length(mEntries
) > 0) then result
:= TValEnumerator
.Create(mEntries
, mFirstEntry
, mLastEntry
)
940 else result
:= TValEnumerator
.Create(nil, -1, -1);
943 function THashBase
.byKey (): TKeyEnumerator
;
945 if (Length(mEntries
) > 0) then result
:= TKeyEnumerator
.Create(mEntries
, mFirstEntry
, mLastEntry
)
946 else result
:= TKeyEnumerator
.Create(nil, -1, -1);
949 function THashBase
.byValue (): TValEnumerator
;
951 if (Length(mEntries
) > 0) then result
:= TValEnumerator
.Create(mEntries
, mFirstEntry
, mLastEntry
)
952 else result
:= TValEnumerator
.Create(nil, -1, -1);
955 function THashBase
.byKeyValue (): TKeyValEnumerator
; // PEntry
957 if (Length(mEntries
) > 0) then result
:= TKeyValEnumerator
.Create(mEntries
, mFirstEntry
, mLastEntry
)
958 else result
:= TKeyValEnumerator
.Create(nil, -1, -1);
962 // ////////////////////////////////////////////////////////////////////////// //
963 constructor THashBase
.TValEnumerator
.Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
966 mFirstEntry
:= afirst
;
968 cur
:= mFirstEntry
-1;
971 function THashBase
.TValEnumerator
.MoveNext (): Boolean; inline;
974 while (cur
<= mLastEntry
) do
976 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
981 function THashBase
.TValEnumerator
.getCurrent (): ValueT
; inline;
983 result
:= mEntries
[cur
].value
;
987 // ////////////////////////////////////////////////////////////////////////// //
988 constructor THashBase
.TKeyEnumerator
.Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
991 mFirstEntry
:= afirst
;
993 cur
:= mFirstEntry
-1;
996 function THashBase
.TKeyEnumerator
.MoveNext (): Boolean; inline;
999 while (cur
<= mLastEntry
) do
1001 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
1006 function THashBase
.TKeyEnumerator
.getCurrent (): KeyT
; inline;
1008 result
:= mEntries
[cur
].key
;
1012 // ////////////////////////////////////////////////////////////////////////// //
1013 constructor THashBase
.TKeyValEnumerator
.Create (const aents
: TEntryArray
; afirst
, alast
: Integer);
1016 mFirstEntry
:= afirst
;
1017 mLastEntry
:= alast
;
1018 cur
:= mFirstEntry
-1;
1021 function THashBase
.TKeyValEnumerator
.MoveNext (): Boolean; inline;
1024 while (cur
<= mLastEntry
) do
1026 if (mEntries
[cur
].hash
<> 0) then begin result
:= true; exit
; end;
1031 function THashBase
.TKeyValEnumerator
.getCurrent (): PEntry
; inline;
1033 result
:= @mEntries
[cur
];