1 (* Copyright (C) DooM 2D:Forever Developers
2 *
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.
7 *
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.
12 *
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/>.
15 *)
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}
23 {$ENDIF}
24 // hash table (robin hood)
27 interface
30 type
31 // WARNING! don't put structures into hash, use ponters or ids!
33 private
37 public
40 type TIteratorFn = function (constref k: KeyT; constref v: ValueT): Boolean is nested; // return `true` to stop
42 type
45 public
48 private
53 private
54 type
58 private
61 public
69 private
72 public
80 private
83 public
90 private
96 {$IFDEF RBHASH_SANITY_CHECKS}
98 {$ENDIF}
103 private
107 //function distToStIdx (idx: LongWord): LongWord; inline;
113 public
128 //WARNING! don't modify table in iterator (queries are ok, though)
131 // default `for ... in` enums values
141 type
143 private
147 public
155 // current hash value
156 // you can continue putting data, as this is not destructive
161 type
176 implementation
178 uses
179 SysUtils;
182 // ////////////////////////////////////////////////////////////////////////// //
183 {$PUSH}
184 {$RANGECHECKS OFF}
186 begin
193 // already pot?
194 if (x <> 0) and ((x and (x-1)) = 0) then result := result and (not (result shr 1)) else result += 1;
196 {$POP}
199 // ////////////////////////////////////////////////////////////////////////// //
203 {$PUSH}
204 {$RANGECHECKS OFF}
206 begin
218 begin
221 {$POP}
225 begin
231 begin
236 // ////////////////////////////////////////////////////////////////////////// //
237 {$PUSH}
238 {$RANGECHECKS OFF}
240 begin
246 begin
252 begin
259 var
262 begin
267 begin
279 begin
285 {$POP}
289 var
291 begin
298 // ////////////////////////////////////////////////////////////////////////// //
299 {$PUSH}
300 {$RANGECHECKS OFF}
301 // fnv-1a: http://www.isthe.com/chongo/tech/comp/fnv/
303 var
305 begin
309 begin
316 {$POP}
319 {$PUSH}
320 {$RANGECHECKS OFF}
322 begin
332 {$POP}
335 // ////////////////////////////////////////////////////////////////////////// //
337 begin
338 if not assigned(ahashfn) then raise Exception.Create('cannot create hash without hash function');
339 if not assigned(aequfn) then raise Exception.Create('cannot create hash without equality function');
350 begin
358 var
360 begin
365 {
366 for idx := 0 to High(mEntries)-1 do
367 begin
368 mEntries[idx].hash := 0;
369 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
370 end;
371 mEntries[High(mEntries)].hash := 0;
372 mEntries[High(mEntries)].nextFree := nil;
373 }
374 {
375 for idx := 0 to High(mEntries) do
376 begin
377 mEntries[idx].hash := 0;
378 mEntries[idx].nextFree := nil;
379 end;
380 }
383 {$IFDEF RBHASH_SANITY_CHECKS}
385 {$ENDIF}
393 var
395 begin
397 begin
399 {
400 for idx := 0 to High(mEntries)-1 do
401 begin
402 mEntries[idx].hash := 0;
403 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
404 end;
405 mEntries[High(mEntries)].hash := 0;
406 mEntries[High(mEntries)].nextFree := nil;
407 }
408 {
409 if (mFirstEntry >= 0) then
410 begin
411 for idx := mFirstEntry to mLastEntry do
412 begin
413 mEntries[idx].hash := 0;
414 mEntries[idx].nextFree := nil;
415 end;
416 end;
417 }
420 {$IFDEF RBHASH_SANITY_CHECKS}
422 {$ENDIF}
434 var
436 begin
438 begin
439 if (mLastEntry = High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (0.0)');
442 begin
443 if (mLastEntry <> 0) then raise Exception.Create('internal error in hash entry allocator (0.1)');
448 {$IFDEF RBHASH_SANITY_CHECKS}
450 {$ENDIF}
451 exit;
453 {$IFDEF RBHASH_SANITY_CHECKS}
454 if (mFreeEntryHead = nil) then raise Exception.Create('internal error in hash entry allocator (0)');
455 if (mFreeEntryHead.hash <> 0) then raise Exception.Create('internal error in hash entry allocator (1)');
456 {$ENDIF}
459 {$IFDEF RBHASH_SANITY_CHECKS}
461 {$ENDIF}
463 // fix mFirstEntry and mLastEntry
465 {$IFDEF RBHASH_SANITY_CHECKS}
466 if (idx < 0) or (idx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid entry address)');
467 if (result <> @mEntries[idx]) then raise Exception.Create('internal error in hash entry allocator (wtf?!)');
468 {$ENDIF}
475 var
477 begin
478 {$IFDEF RBHASH_SANITY_CHECKS}
480 if (mEntriesUsed <> mBucketsUsed) then raise Exception.Create('internal error in hash entry allocator (entry/bucket count mismatch)');
481 if (e = nil) then raise Exception.Create('internal error in hash entry allocator (trying to release nil entry)');
482 if (e.hash = 0) then raise Exception.Create('internal error in hash entry allocator (trying to release unallocated entry)');
483 {$ENDIF}
485 {$IFDEF RBHASH_SANITY_CHECKS}
486 if (idx < 0) or (idx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid entry address)');
487 if (e <> @mEntries[idx]) then raise Exception.Create('internal error in hash entry allocator (wtf?!)');
488 {$ENDIF}
489 {$IFDEF RBHASH_SANITY_CHECKS}
491 {$ENDIF}
495 // fix mFirstEntry and mLastEntry
496 {$IFDEF RBHASH_SANITY_CHECKS}
497 if (mFirstEntry < 0) or (mLastEntry < 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 0)');
498 {$ENDIF}
500 begin
501 {$IFDEF RBHASH_SANITY_CHECKS}
502 if (mEntriesUsed <> 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 1)');
503 {$ENDIF}
506 end
507 else
508 begin
509 {$IFDEF RBHASH_SANITY_CHECKS}
510 if (mEntriesUsed = 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 2)');
511 {$ENDIF}
512 // fix first entry index
514 begin
517 {$IFDEF RBHASH_SANITY_CHECKS}
518 if (cidx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 3)');
519 {$ENDIF}
522 // fix last entry index
524 begin
527 {$IFDEF RBHASH_SANITY_CHECKS}
528 if (cidx < 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 3)');
529 {$ENDIF}
536 (*
537 function THashBase.distToStIdx (idx: LongWord): LongWord; inline;
538 begin
539 {$IFDEF RBHASH_SANITY_CHECKS}
540 assert(idx < Length(mBuckets));
541 assert(mBuckets[idx] <> nil);
542 {$ENDIF}
543 result := mBuckets[idx].hash and High(mBuckets);
544 if (result <= idx) then result := idx-result else result := idx+(Length(mBuckets)-result);
545 end;
546 *)
550 var
554 begin
564 begin
566 //pdist := distToStIdx(idx);
569 //
579 var
583 begin
593 begin
595 //pdist := distToStIdx(idx);
598 //
602 begin
604 break;
614 var
618 begin
621 {$IFDEF RBHASH_DEBUG_INSERT}writeln('inserting key ', swpe.key, '; value=', swpe.value, '; wantidx=', idx, '; bhigh=', bhigh);{$ENDIF}
624 begin
626 begin
627 // put entry
631 break;
633 //pdist := distToStIdx(idx);
636 //
638 begin
639 // swapping the current bucket with the one to insert
652 var
657 begin
664 // check if we already have this key
666 begin
668 begin
670 //pdist := distToStIdx(idx);
673 //
677 begin
678 // replace element
679 //mBuckets[idx].key := akey;
681 exit;
687 // need to resize hash?
689 begin
691 if (Length(mEntries) <> newsz) then raise Exception.Create('internal error in hash table (resize)');
693 {$IFDEF RBHASH_DEBUG_RESIZE}
694 writeln('resizing hash; used=', mBucketsUsed, '; total=', (bhigh+1), '; maxload=', (bhigh+1)*LoadFactorPrc div 100, '; newsz=', newsz);
695 {$ENDIF}
697 // resize entries array
701 // mFreeEntryHead will be fixed in `rehash()`
702 // reinsert entries
704 // as seed was changed, recalc hash
708 // create new entry
718 // see http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
720 var
723 begin
731 // find key
734 begin
736 //pdist := distToStIdx(idxcur);
739 //
747 begin
748 // key not found
749 {$IFDEF RBHASH_DEBUG_DELETE}
751 {$ENDIF}
752 exit;
755 {$IFDEF RBHASH_DEBUG_DELETE}
756 writeln('del: key ', akey, ': found at ', idx, '; ek=', mBuckets[idx].key, '; ev=', mBuckets[idx].value);
757 {$ENDIF}
762 begin
763 {$IFDEF RBHASH_DEBUG_DELETE}
764 writeln(' dist=', dist, '; idx=', idx, '; idxnext=', idxnext, '; ce=', (mBuckets[idx] <> nil), '; ne=', (mBuckets[idxnext] <> nil));
765 {$ENDIF}
766 if (mBuckets[idxnext] = nil) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' idxnext nil');{$ENDIF} mBuckets[idx] := nil; break; end;
767 //pdist := distToStIdx(idxnext);
770 //
771 if (pdist = 0) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist is zero');{$ENDIF} mBuckets[idx] := nil; break; end;
783 var
787 {$IFDEF RBHASH_SANITY_CHECKS}
789 {$ENDIF}
790 begin
791 // change seed, to minimize pathological cases
794 // clear buckets
797 // reinsert entries
801 begin
804 begin
805 {$IFDEF RBHASH_SANITY_CHECKS}
807 if (cnt = 0) and (idx <> mFirstEntry) then raise Exception.Create('internal error in rehash: inconsistent (1)');
809 if (cnt = mBucketsUsed) and (idx <> mLastEntry) then raise Exception.Create('internal error in rehash: inconsistent (2)');
810 {$ENDIF}
813 end
814 else
815 begin
821 {$IFDEF RBHASH_SANITY_CHECKS}
822 if (cnt <> mBucketsUsed) then raise Exception.Create('internal error in hash table resize (invalid first/last range; 0)');
823 if (cnt <> mEntriesUsed) then raise Exception.Create('internal error in hash table resize (invalid first/last range; 1)');
824 {$ENDIF}
829 var
831 {$IFDEF RBHASH_SANITY_CHECKS}
833 {$ENDIF}
834 begin
839 {$IFDEF RBHASH_DEBUG_COMPACT}writeln('compacting; used=', mBucketsUsed, '; oldsizePOT=', newsz, '; newsize=', newsz*2);{$ENDIF}
841 // move all entries to top
843 begin
844 {$IFDEF RBHASH_SANITY_CHECKS}
845 if (mBucketsUsed < 1) then raise Exception.Create('internal error in hash table (invalid bucket count; 0)');
846 {$ENDIF}
850 // copy entries
852 begin
854 begin
855 {$IFDEF RBHASH_SANITY_CHECKS}
857 {$ENDIF}
866 {$IFDEF RBHASH_SANITY_CHECKS}
867 if (didx <> mBucketsUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 1)');
868 {$ENDIF}
871 {$IFDEF RBHASH_SANITY_CHECKS}
874 begin
875 if (mEntries[f].hash = 0) then raise Exception.Create('internal error in hash table (invalid first/last range; 2)');
878 if (cnt <> mBucketsUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 3)');
879 if (cnt <> mEntriesUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 4)');
881 begin
882 if (mEntries[f].hash <> 0) then raise Exception.Create('internal error in hash table (invalid first/last range; 5)');
884 {$ENDIF}
885 end
886 else
887 begin
888 {$IFDEF RBHASH_SANITY_CHECKS}
889 if (mBucketsUsed <> 0) then raise Exception.Create('internal error in hash table (invalid bucket count; 1)');
890 {$ENDIF}
892 // shrink
895 // mFreeEntryHead will be fixed in `rehash()`
896 // reinsert entries
902 var
904 begin
910 begin
912 begin
921 // enumerators
923 begin
924 if (Length(mEntries) > 0) then result := TValEnumerator.Create(mEntries, mFirstEntry, mLastEntry)
929 begin
930 if (Length(mEntries) > 0) then result := TKeyEnumerator.Create(mEntries, mFirstEntry, mLastEntry)
935 begin
936 if (Length(mEntries) > 0) then result := TValEnumerator.Create(mEntries, mFirstEntry, mLastEntry)
941 begin
942 if (Length(mEntries) > 0) then result := TKeyValEnumerator.Create(mEntries, mFirstEntry, mLastEntry)
947 // ////////////////////////////////////////////////////////////////////////// //
949 begin
957 begin
960 begin
967 begin
972 // ////////////////////////////////////////////////////////////////////////// //
974 begin
982 begin
985 begin
992 begin
997 // ////////////////////////////////////////////////////////////////////////// //
998 constructor THashBase.TKeyValEnumerator.Create (const aents: TEntryArray; afirst, alast: Integer);
999 begin
1007 begin
1010 begin
1017 begin