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 private
43 type
52 private
58 {$IFDEF RBHASH_SANITY_CHECKS}
60 {$ENDIF}
65 private
69 //function distToStIdx (idx: LongWord): LongWord; inline;
75 public
90 //WARNING! don't modify table in iterator (queries are ok, though)
98 type
100 private
104 public
112 // current hash value
113 // you can continue putting data, as this is not destructive
118 type
131 implementation
133 uses
134 SysUtils;
137 // ////////////////////////////////////////////////////////////////////////// //
138 {$PUSH}
139 {$RANGECHECKS OFF}
141 begin
148 // already pot?
149 if (x <> 0) and ((x and (x-1)) = 0) then result := result and (not (result shr 1)) else result += 1;
151 {$POP}
154 // ////////////////////////////////////////////////////////////////////////// //
157 {$PUSH}
158 {$RANGECHECKS OFF}
160 begin
170 {$POP}
174 begin
179 // ////////////////////////////////////////////////////////////////////////// //
180 {$PUSH}
181 {$RANGECHECKS OFF}
183 begin
189 begin
195 begin
202 var
205 begin
210 begin
222 begin
228 {$POP}
232 var
234 begin
241 // ////////////////////////////////////////////////////////////////////////// //
242 {$PUSH}
243 {$RANGECHECKS OFF}
244 // fnv-1a: http://www.isthe.com/chongo/tech/comp/fnv/
246 var
248 begin
252 begin
259 {$POP}
262 {$PUSH}
263 {$RANGECHECKS OFF}
265 begin
275 {$POP}
278 // ////////////////////////////////////////////////////////////////////////// //
280 begin
281 if not assigned(ahashfn) then raise Exception.Create('cannot create hash without hash function');
282 if not assigned(aequfn) then raise Exception.Create('cannot create hash without equality function');
293 begin
301 var
303 begin
308 {
309 for idx := 0 to High(mEntries)-1 do
310 begin
311 mEntries[idx].hash := 0;
312 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
313 end;
314 mEntries[High(mEntries)].hash := 0;
315 mEntries[High(mEntries)].nextFree := nil;
316 }
317 {
318 for idx := 0 to High(mEntries) do
319 begin
320 mEntries[idx].hash := 0;
321 mEntries[idx].nextFree := nil;
322 end;
323 }
326 {$IFDEF RBHASH_SANITY_CHECKS}
328 {$ENDIF}
336 var
338 begin
340 begin
342 {
343 for idx := 0 to High(mEntries)-1 do
344 begin
345 mEntries[idx].hash := 0;
346 mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1;
347 end;
348 mEntries[High(mEntries)].hash := 0;
349 mEntries[High(mEntries)].nextFree := nil;
350 }
351 {
352 if (mFirstEntry >= 0) then
353 begin
354 for idx := mFirstEntry to mLastEntry do
355 begin
356 mEntries[idx].hash := 0;
357 mEntries[idx].nextFree := nil;
358 end;
359 end;
360 }
363 {$IFDEF RBHASH_SANITY_CHECKS}
365 {$ENDIF}
377 var
379 begin
381 begin
382 if (mLastEntry = High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (0.0)');
385 begin
386 if (mLastEntry <> 0) then raise Exception.Create('internal error in hash entry allocator (0.1)');
391 {$IFDEF RBHASH_SANITY_CHECKS}
393 {$ENDIF}
394 exit;
396 {$IFDEF RBHASH_SANITY_CHECKS}
397 if (mFreeEntryHead = nil) then raise Exception.Create('internal error in hash entry allocator (0)');
398 if (mFreeEntryHead.hash <> 0) then raise Exception.Create('internal error in hash entry allocator (1)');
399 {$ENDIF}
402 {$IFDEF RBHASH_SANITY_CHECKS}
404 {$ENDIF}
406 // fix mFirstEntry and mLastEntry
408 {$IFDEF RBHASH_SANITY_CHECKS}
409 if (idx < 0) or (idx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid entry address)');
410 if (result <> @mEntries[idx]) then raise Exception.Create('internal error in hash entry allocator (wtf?!)');
411 {$ENDIF}
418 var
420 begin
421 {$IFDEF RBHASH_SANITY_CHECKS}
423 if (mEntriesUsed <> mBucketsUsed) then raise Exception.Create('internal error in hash entry allocator (entry/bucket count mismatch)');
424 if (e = nil) then raise Exception.Create('internal error in hash entry allocator (trying to release nil entry)');
425 if (e.hash = 0) then raise Exception.Create('internal error in hash entry allocator (trying to release unallocated entry)');
426 {$ENDIF}
428 {$IFDEF RBHASH_SANITY_CHECKS}
429 if (idx < 0) or (idx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid entry address)');
430 if (e <> @mEntries[idx]) then raise Exception.Create('internal error in hash entry allocator (wtf?!)');
431 {$ENDIF}
432 {$IFDEF RBHASH_SANITY_CHECKS}
434 {$ENDIF}
438 // fix mFirstEntry and mLastEntry
439 {$IFDEF RBHASH_SANITY_CHECKS}
440 if (mFirstEntry < 0) or (mLastEntry < 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 0)');
441 {$ENDIF}
443 begin
444 {$IFDEF RBHASH_SANITY_CHECKS}
445 if (mEntriesUsed <> 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 1)');
446 {$ENDIF}
449 end
450 else
451 begin
452 {$IFDEF RBHASH_SANITY_CHECKS}
453 if (mEntriesUsed = 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 2)');
454 {$ENDIF}
455 // fix first entry index
457 begin
460 {$IFDEF RBHASH_SANITY_CHECKS}
461 if (cidx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 3)');
462 {$ENDIF}
465 // fix last entry index
467 begin
470 {$IFDEF RBHASH_SANITY_CHECKS}
471 if (cidx < 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 3)');
472 {$ENDIF}
479 (*
480 function THashBase.distToStIdx (idx: LongWord): LongWord; inline;
481 begin
482 {$IFDEF RBHASH_SANITY_CHECKS}
483 assert(idx < Length(mBuckets));
484 assert(mBuckets[idx] <> nil);
485 {$ENDIF}
486 result := mBuckets[idx].hash and High(mBuckets);
487 if (result <= idx) then result := idx-result else result := idx+(Length(mBuckets)-result);
488 end;
489 *)
493 var
497 begin
507 begin
509 //pdist := distToStIdx(idx);
512 //
522 var
526 begin
536 begin
538 //pdist := distToStIdx(idx);
541 //
545 begin
547 break;
557 var
561 begin
564 {$IFDEF RBHASH_DEBUG_INSERT}writeln('inserting key ', swpe.key, '; value=', swpe.value, '; wantidx=', idx, '; bhigh=', bhigh);{$ENDIF}
567 begin
569 begin
570 // put entry
574 break;
576 //pdist := distToStIdx(idx);
579 //
581 begin
582 // swapping the current bucket with the one to insert
595 var
600 begin
607 // check if we already have this key
609 begin
611 begin
613 //pdist := distToStIdx(idx);
616 //
620 begin
621 // replace element
622 //mBuckets[idx].key := akey;
624 exit;
630 // need to resize hash?
632 begin
634 if (Length(mEntries) <> newsz) then raise Exception.Create('internal error in hash table (resize)');
636 {$IFDEF RBHASH_DEBUG_RESIZE}
637 writeln('resizing hash; used=', mBucketsUsed, '; total=', (bhigh+1), '; maxload=', (bhigh+1)*LoadFactorPrc div 100, '; newsz=', newsz);
638 {$ENDIF}
640 // resize entries array
644 // mFreeEntryHead will be fixed in `rehash()`
645 // reinsert entries
647 // as seed was changed, recalc hash
651 // create new entry
661 // see http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
663 var
666 begin
674 // find key
677 begin
679 //pdist := distToStIdx(idxcur);
682 //
690 begin
691 // key not found
692 {$IFDEF RBHASH_DEBUG_DELETE}
694 {$ENDIF}
695 exit;
698 {$IFDEF RBHASH_DEBUG_DELETE}
699 writeln('del: key ', akey, ': found at ', idx, '; ek=', mBuckets[idx].key, '; ev=', mBuckets[idx].value);
700 {$ENDIF}
705 begin
706 {$IFDEF RBHASH_DEBUG_DELETE}
707 writeln(' dist=', dist, '; idx=', idx, '; idxnext=', idxnext, '; ce=', (mBuckets[idx] <> nil), '; ne=', (mBuckets[idxnext] <> nil));
708 {$ENDIF}
709 if (mBuckets[idxnext] = nil) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' idxnext nil');{$ENDIF} mBuckets[idx] := nil; break; end;
710 //pdist := distToStIdx(idxnext);
713 //
714 if (pdist = 0) then begin {$IFDEF RBHASH_DEBUG_DELETE}writeln(' pdist is zero');{$ENDIF} mBuckets[idx] := nil; break; end;
726 var
730 {$IFDEF RBHASH_SANITY_CHECKS}
732 {$ENDIF}
733 begin
734 // change seed, to minimize pathological cases
737 // clear buckets
740 // reinsert entries
744 begin
747 begin
748 {$IFDEF RBHASH_SANITY_CHECKS}
750 if (cnt = 0) and (idx <> mFirstEntry) then raise Exception.Create('internal error in rehash: inconsistent (1)');
752 if (cnt = mBucketsUsed) and (idx <> mLastEntry) then raise Exception.Create('internal error in rehash: inconsistent (2)');
753 {$ENDIF}
756 end
757 else
758 begin
764 {$IFDEF RBHASH_SANITY_CHECKS}
765 if (cnt <> mBucketsUsed) then raise Exception.Create('internal error in hash table resize (invalid first/last range; 0)');
766 if (cnt <> mEntriesUsed) then raise Exception.Create('internal error in hash table resize (invalid first/last range; 1)');
767 {$ENDIF}
772 var
774 {$IFDEF RBHASH_SANITY_CHECKS}
776 {$ENDIF}
777 begin
782 {$IFDEF RBHASH_DEBUG_COMPACT}writeln('compacting; used=', mBucketsUsed, '; oldsizePOT=', newsz, '; newsize=', newsz*2);{$ENDIF}
784 // move all entries to top
786 begin
787 {$IFDEF RBHASH_SANITY_CHECKS}
788 if (mBucketsUsed < 1) then raise Exception.Create('internal error in hash table (invalid bucket count; 0)');
789 {$ENDIF}
793 // copy entries
795 begin
797 begin
798 {$IFDEF RBHASH_SANITY_CHECKS}
800 {$ENDIF}
809 {$IFDEF RBHASH_SANITY_CHECKS}
810 if (didx <> mBucketsUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 1)');
811 {$ENDIF}
814 {$IFDEF RBHASH_SANITY_CHECKS}
817 begin
818 if (mEntries[f].hash = 0) then raise Exception.Create('internal error in hash table (invalid first/last range; 2)');
821 if (cnt <> mBucketsUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 3)');
822 if (cnt <> mEntriesUsed) then raise Exception.Create('internal error in hash table (invalid first/last range; 4)');
824 begin
825 if (mEntries[f].hash <> 0) then raise Exception.Create('internal error in hash table (invalid first/last range; 5)');
827 {$ENDIF}
828 end
829 else
830 begin
831 {$IFDEF RBHASH_SANITY_CHECKS}
832 if (mBucketsUsed <> 0) then raise Exception.Create('internal error in hash table (invalid bucket count; 1)');
833 {$ENDIF}
835 // shrink
838 // mFreeEntryHead will be fixed in `rehash()`
839 // reinsert entries
845 var
847 begin
853 begin
855 begin