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
56 private
59 public
67 private
70 public
78 private
81 public
88 private
94 {$IFDEF RBHASH_SANITY_CHECKS}
96 {$ENDIF}
101 private
105 //function distToStIdx (idx: LongWord): LongWord; inline;
111 public
126 //WARNING! don't modify table in iterator (queries are ok, though)
129 // default `for ... in` enums values
139 type
141 private
145 public
153 // current hash value
154 // you can continue putting data, as this is not destructive
159 type
172 implementation
174 uses
175 SysUtils;
178 // ////////////////////////////////////////////////////////////////////////// //
179 {$PUSH}
180 {$RANGECHECKS OFF}
182 begin
189 // already pot?
190 if (x <> 0) and ((x and (x-1)) = 0) then result := result and (not (result shr 1)) else result += 1;
192 {$POP}
195 // ////////////////////////////////////////////////////////////////////////// //
198 {$PUSH}
199 {$RANGECHECKS OFF}
201 begin
211 {$POP}
215 begin
220 // ////////////////////////////////////////////////////////////////////////// //
221 {$PUSH}
222 {$RANGECHECKS OFF}
224 begin
230 begin
236 begin
243 var
246 begin
251 begin
263 begin
269 {$POP}
273 var
275 begin
282 // ////////////////////////////////////////////////////////////////////////// //
283 {$PUSH}
284 {$RANGECHECKS OFF}
285 // fnv-1a: http://www.isthe.com/chongo/tech/comp/fnv/
287 var
289 begin
293 begin
300 {$POP}
303 {$PUSH}
304 {$RANGECHECKS OFF}
306 begin
316 {$POP}
319 // ////////////////////////////////////////////////////////////////////////// //
321 begin
322 if not assigned(ahashfn) then raise Exception.Create('cannot create hash without hash function');
323 if not assigned(aequfn) then raise Exception.Create('cannot create hash without equality function');
334 begin
342 var
344 begin
349 {
350 for idx := 0 to High(mEntries)-1 do
351 begin
352 mEntries[idx].hash := 0;
353 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
354 end;
355 mEntries[High(mEntries)].hash := 0;
356 mEntries[High(mEntries)].nextFree := nil;
357 }
358 {
359 for idx := 0 to High(mEntries) do
360 begin
361 mEntries[idx].hash := 0;
362 mEntries[idx].nextFree := nil;
363 end;
364 }
367 {$IFDEF RBHASH_SANITY_CHECKS}
369 {$ENDIF}
377 var
379 begin
381 begin
383 {
384 for idx := 0 to High(mEntries)-1 do
385 begin
386 mEntries[idx].hash := 0;
387 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
388 end;
389 mEntries[High(mEntries)].hash := 0;
390 mEntries[High(mEntries)].nextFree := nil;
391 }
392 {
393 if (mFirstEntry >= 0) then
394 begin
395 for idx := mFirstEntry to mLastEntry do
396 begin
397 mEntries[idx].hash := 0;
398 mEntries[idx].nextFree := nil;
399 end;
400 end;
401 }
404 {$IFDEF RBHASH_SANITY_CHECKS}
406 {$ENDIF}
418 var
420 begin
422 begin
423 if (mLastEntry = High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (0.0)');
426 begin
427 if (mLastEntry <> 0) then raise Exception.Create('internal error in hash entry allocator (0.1)');
432 {$IFDEF RBHASH_SANITY_CHECKS}
434 {$ENDIF}
435 exit;
437 {$IFDEF RBHASH_SANITY_CHECKS}
438 if (mFreeEntryHead = nil) then raise Exception.Create('internal error in hash entry allocator (0)');
439 if (mFreeEntryHead.hash <> 0) then raise Exception.Create('internal error in hash entry allocator (1)');
440 {$ENDIF}
443 {$IFDEF RBHASH_SANITY_CHECKS}
445 {$ENDIF}
447 // fix mFirstEntry and mLastEntry
449 {$IFDEF RBHASH_SANITY_CHECKS}
450 if (idx < 0) or (idx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid entry address)');
451 if (result <> @mEntries[idx]) then raise Exception.Create('internal error in hash entry allocator (wtf?!)');
452 {$ENDIF}
459 var
461 begin
462 {$IFDEF RBHASH_SANITY_CHECKS}
464 if (mEntriesUsed <> mBucketsUsed) then raise Exception.Create('internal error in hash entry allocator (entry/bucket count mismatch)');
465 if (e = nil) then raise Exception.Create('internal error in hash entry allocator (trying to release nil entry)');
466 if (e.hash = 0) then raise Exception.Create('internal error in hash entry allocator (trying to release unallocated entry)');
467 {$ENDIF}
469 {$IFDEF RBHASH_SANITY_CHECKS}
470 if (idx < 0) or (idx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid entry address)');
471 if (e <> @mEntries[idx]) then raise Exception.Create('internal error in hash entry allocator (wtf?!)');
472 {$ENDIF}
473 {$IFDEF RBHASH_SANITY_CHECKS}
475 {$ENDIF}
479 // fix mFirstEntry and mLastEntry
480 {$IFDEF RBHASH_SANITY_CHECKS}
481 if (mFirstEntry < 0) or (mLastEntry < 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 0)');
482 {$ENDIF}
484 begin
485 {$IFDEF RBHASH_SANITY_CHECKS}
486 if (mEntriesUsed <> 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 1)');
487 {$ENDIF}
490 end
491 else
492 begin
493 {$IFDEF RBHASH_SANITY_CHECKS}
494 if (mEntriesUsed = 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 2)');
495 {$ENDIF}
496 // fix first entry index
498 begin
501 {$IFDEF RBHASH_SANITY_CHECKS}
502 if (cidx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 3)');
503 {$ENDIF}
506 // fix last entry index
508 begin
511 {$IFDEF RBHASH_SANITY_CHECKS}
512 if (cidx < 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 3)');
513 {$ENDIF}
520 (*
521 function THashBase.distToStIdx (idx: LongWord): LongWord; inline;
522 begin
523 {$IFDEF RBHASH_SANITY_CHECKS}
524 assert(idx < Length(mBuckets));
525 assert(mBuckets[idx] <> nil);
526 {$ENDIF}
527 result := mBuckets[idx].hash and High(mBuckets);
528 if (result <= idx) then result := idx-result else result := idx+(Length(mBuckets)-result);
529 end;
530 *)
534 var
538 begin
548 begin
550 //pdist := distToStIdx(idx);
553 //
563 var
567 begin
577 begin
579 //pdist := distToStIdx(idx);
582 //
586 begin
588 break;
598 var
602 begin
605 {$IFDEF RBHASH_DEBUG_INSERT}writeln('inserting key ', swpe.key, '; value=', swpe.value, '; wantidx=', idx, '; bhigh=', bhigh);{$ENDIF}
608 begin
610 begin
611 // put entry
615 break;
617 //pdist := distToStIdx(idx);
620 //
622 begin
623 // swapping the current bucket with the one to insert
636 var
641 begin
648 // check if we already have this key
650 begin
652 begin
654 //pdist := distToStIdx(idx);
657 //
661 begin
662 // replace element
663 //mBuckets[idx].key := akey;
665 exit;
671 // need to resize hash?
673 begin
675 if (Length(mEntries) <> newsz) then raise Exception.Create('internal error in hash table (resize)');
677 {$IFDEF RBHASH_DEBUG_RESIZE}
678 writeln('resizing hash; used=', mBucketsUsed, '; total=', (bhigh+1), '; maxload=', (bhigh+1)*LoadFactorPrc div 100, '; newsz=', newsz);
679 {$ENDIF}
681 // resize entries array
685 // mFreeEntryHead will be fixed in `rehash()`
686 // reinsert entries
688 // as seed was changed, recalc hash
692 // create new entry
702 // see http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
704 var
707 begin
715 // find key
718 begin
720 //pdist := distToStIdx(idxcur);
723 //
731 begin
732 // key not found
733 {$IFDEF RBHASH_DEBUG_DELETE}
735 {$ENDIF}
736 exit;
739 {$IFDEF RBHASH_DEBUG_DELETE}
740 writeln('del: key ', akey, ': found at ', idx, '; ek=', mBuckets[idx].key, '; ev=', mBuckets[idx].value);
741 {$ENDIF}
746 begin
747 {$IFDEF RBHASH_DEBUG_DELETE}
748 writeln(' dist=', dist, '; idx=', idx, '; idxnext=', idxnext, '; ce=', (mBuckets[idx] <> nil), '; ne=', (mBuckets[idxnext] <> nil));
749 {$ENDIF}
750 if (mBuckets[idxnext] = nil) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' idxnext nil');{$ENDIF} mBuckets[idx] := nil; break; end;
751 //pdist := distToStIdx(idxnext);
754 //
755 if (pdist = 0) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist is zero');{$ENDIF} mBuckets[idx] := nil; break; end;
767 var
771 {$IFDEF RBHASH_SANITY_CHECKS}
773 {$ENDIF}
774 begin
775 // change seed, to minimize pathological cases
778 // clear buckets
781 // reinsert entries
785 begin
788 begin
789 {$IFDEF RBHASH_SANITY_CHECKS}
791 if (cnt = 0) and (idx <> mFirstEntry) then raise Exception.Create('internal error in rehash: inconsistent (1)');
793 if (cnt = mBucketsUsed) and (idx <> mLastEntry) then raise Exception.Create('internal error in rehash: inconsistent (2)');
794 {$ENDIF}
797 end
798 else
799 begin
805 {$IFDEF RBHASH_SANITY_CHECKS}
806 if (cnt <> mBucketsUsed) then raise Exception.Create('internal error in hash table resize (invalid first/last range; 0)');
807 if (cnt <> mEntriesUsed) then raise Exception.Create('internal error in hash table resize (invalid first/last range; 1)');
808 {$ENDIF}
813 var
815 {$IFDEF RBHASH_SANITY_CHECKS}
817 {$ENDIF}
818 begin
823 {$IFDEF RBHASH_DEBUG_COMPACT}writeln('compacting; used=', mBucketsUsed, '; oldsizePOT=', newsz, '; newsize=', newsz*2);{$ENDIF}
825 // move all entries to top
827 begin
828 {$IFDEF RBHASH_SANITY_CHECKS}
829 if (mBucketsUsed < 1) then raise Exception.Create('internal error in hash table (invalid bucket count; 0)');
830 {$ENDIF}
834 // copy entries
836 begin
838 begin
839 {$IFDEF RBHASH_SANITY_CHECKS}
841 {$ENDIF}
850 {$IFDEF RBHASH_SANITY_CHECKS}
851 if (didx <> mBucketsUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 1)');
852 {$ENDIF}
855 {$IFDEF RBHASH_SANITY_CHECKS}
858 begin
859 if (mEntries[f].hash = 0) then raise Exception.Create('internal error in hash table (invalid first/last range; 2)');
862 if (cnt <> mBucketsUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 3)');
863 if (cnt <> mEntriesUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 4)');
865 begin
866 if (mEntries[f].hash <> 0) then raise Exception.Create('internal error in hash table (invalid first/last range; 5)');
868 {$ENDIF}
869 end
870 else
871 begin
872 {$IFDEF RBHASH_SANITY_CHECKS}
873 if (mBucketsUsed <> 0) then raise Exception.Create('internal error in hash table (invalid bucket count; 1)');
874 {$ENDIF}
876 // shrink
879 // mFreeEntryHead will be fixed in `rehash()`
880 // reinsert entries
886 var
888 begin
894 begin
896 begin
905 // enumerators
907 begin
908 if (Length(mEntries) > 0) then result := TValEnumerator.Create(@mEntries[0], mFirstEntry, mLastEntry)
913 begin
914 if (Length(mEntries) > 0) then result := TKeyEnumerator.Create(@mEntries[0], mFirstEntry, mLastEntry)
919 begin
920 if (Length(mEntries) > 0) then result := TValEnumerator.Create(@mEntries[0], mFirstEntry, mLastEntry)
925 begin
926 if (Length(mEntries) > 0) then result := TKeyValEnumerator.Create(@mEntries[0], mFirstEntry, mLastEntry)
931 // ////////////////////////////////////////////////////////////////////////// //
933 begin
941 begin
944 begin
951 begin
956 // ////////////////////////////////////////////////////////////////////////// //
958 begin
966 begin
969 begin
976 begin
981 // ////////////////////////////////////////////////////////////////////////// //
983 begin
991 begin
994 begin
1001 begin