X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fshared%2Fhashtable.pas;h=9f72e3ab2f5fb547da371473cc30e87e8b47887b;hb=a4f25c41dfd783a925aa2dab4b9b84753d5c3f18;hp=15d19f6a15e8d6bd296423412e1580114c9552ff;hpb=21b8c729c535950bb33c3c036f3510331462d00d;p=d2df-sdl.git diff --git a/src/shared/hashtable.pas b/src/shared/hashtable.pas index 15d19f6..9f72e3a 100644 --- a/src/shared/hashtable.pas +++ b/src/shared/hashtable.pas @@ -39,22 +39,60 @@ type type TEquFn = function (constref a, b: KeyT): Boolean; type TIteratorFn = function (constref k: KeyT; constref v: ValueT): Boolean is nested; // return `true` to stop - private type PEntry = ^TEntry; TEntry = record + public key: KeyT; value: ValueT; + private hash: LongWord; // key hash or 0 nextFree: PEntry; // next free entry end; + private + type + TEntryArray = array of TEntry; + + TValEnumerator = record + private + mEntries: TEntryArray; + mFirstEntry, mLastEntry, cur: Integer; + public + constructor Create (const aents: TEntryArray; afirst, alast: Integer); + function MoveNext (): Boolean; inline; + function getCurrent (): ValueT; inline; + property Current: ValueT read getCurrent; + end; + + TKeyEnumerator = record + private + mEntries: TEntryArray; + mFirstEntry, mLastEntry, cur: Integer; + public + constructor Create (const aents: TEntryArray; afirst, alast: Integer); + function MoveNext (): Boolean; inline; + function getCurrent (): KeyT; inline; + property Current: KeyT read getCurrent; + end; + + TKeyValEnumerator = record + private + mEntries: TEntryArray; + mFirstEntry, mLastEntry, cur: Integer; + public + constructor Create (const aents: TEntryArray; afirst, alast: Integer); + function MoveNext (): Boolean; inline; + function getCurrent (): PEntry; inline; + property Current: PEntry read getCurrent; + end; + private hashfn: THashFn; equfn: TEquFn; mBuckets: array of PEntry; // entries, points to mEntries elements mBucketsUsed: Integer; - mEntries: array of TEntry; + mEntries: TEntryArray; {$IFDEF RBHASH_SANITY_CHECKS} mEntriesUsed: Integer; {$ENDIF} @@ -77,6 +115,7 @@ type destructor Destroy (); override; procedure clear (); + procedure reset (); // don't shrink buckets procedure rehash (); procedure compact (); // call this instead of `rehash()` after alot of deletions @@ -89,11 +128,16 @@ type //WARNING! don't modify table in iterator (queries are ok, though) function forEach (it: TIteratorFn): Boolean; + // default `for ... in` enums values + function GetEnumerator (): TValEnumerator; + function byKey (): TKeyEnumerator; + function byValue (): TValEnumerator; + function byKeyValue (): TKeyValEnumerator; // PEntry + property count: Integer read mBucketsUsed; property capacity: Integer read getCapacity; end; - type TJoaatHasher = record private @@ -106,7 +150,7 @@ type procedure reset (); inline; overload; procedure reset (aseed: LongWord); inline; overload; - procedure put (const buf; len: LongWord); + procedure put (constref buf; len: LongWord); // current hash value // you can continue putting data, as this is not destructive @@ -116,13 +160,15 @@ type type THashIntInt = specialize THashBase; + THashStrInt = specialize THashBase; function hashNewIntInt (): THashIntInt; +function hashNewStrInt (): THashStrInt; function u32Hash (a: LongWord): LongWord; inline; -function fnvHash (const buf; len: LongWord): LongWord; -function joaatHash (const buf; len: LongWord): LongWord; +function fnvHash (constref buf; len: LongWord): LongWord; +function joaatHash (constref buf; len: LongWord): LongWord; function nextPOT (x: LongWord): LongWord; inline; @@ -152,6 +198,7 @@ end; // ////////////////////////////////////////////////////////////////////////// // function hiiequ (constref a, b: Integer): Boolean; begin result := (a = b); end; +function hsiequ (constref a, b: AnsiString): Boolean; begin result := (a = b); end; {$PUSH} {$RANGECHECKS OFF} @@ -166,6 +213,11 @@ begin result := result xor (result shl 10); result := result xor (result shr 15); end; + +function hsihash (constref k: AnsiString): LongWord; +begin + if (Length(k) > 0) then result := fnvHash(PAnsiChar(k)^, Length(k)) else result := 0; +end; {$POP} @@ -175,6 +227,12 @@ begin end; +function hashNewStrInt (): THashStrInt; +begin + result := THashStrInt.Create(hsihash, hsiequ); +end; + + // ////////////////////////////////////////////////////////////////////////// // {$PUSH} {$RANGECHECKS OFF} @@ -197,7 +255,7 @@ begin end; -procedure TJoaatHasher.put (const buf; len: LongWord); +procedure TJoaatHasher.put (constref buf; len: LongWord); var bytes: PByte; h: LongWord; @@ -227,12 +285,12 @@ end; {$POP} -function joaatHash (const buf; len: LongWord): LongWord; +function joaatHash (constref buf; len: LongWord): LongWord; var h: TJoaatHasher; begin h := TJoaatHasher.Create(0); - h.put(buf, len); + h.put(PByte(@buf)^, len); result := h.value; end; @@ -241,7 +299,7 @@ end; {$PUSH} {$RANGECHECKS OFF} // fnv-1a: http://www.isthe.com/chongo/tech/comp/fnv/ -function fnvHash (const buf; len: LongWord): LongWord; +function fnvHash (constref buf; len: LongWord): LongWord; var b: PByte; begin @@ -304,6 +362,7 @@ begin for idx := 0 to High(mBuckets) do mBuckets[idx] := nil; SetLength(mEntries, Length(mBuckets)); + { for idx := 0 to High(mEntries)-1 do begin mEntries[idx].hash := 0; @@ -311,17 +370,63 @@ begin end; mEntries[High(mEntries)].hash := 0; mEntries[High(mEntries)].nextFree := nil; + } + { + for idx := 0 to High(mEntries) do + begin + mEntries[idx].hash := 0; + mEntries[idx].nextFree := nil; + end; + } mBucketsUsed := 0; {$IFDEF RBHASH_SANITY_CHECKS} mEntriesUsed := 0; {$ENDIF} - mFreeEntryHead := @mEntries[0]; + mFreeEntryHead := nil; //@mEntries[0]; mFirstEntry := -1; mLastEntry := -1; end; +procedure THashBase.reset (); +var + idx: Integer; +begin + if (mBucketsUsed > 0) then + begin + for idx := 0 to High(mBuckets) do mBuckets[idx] := nil; + { + for idx := 0 to High(mEntries)-1 do + begin + mEntries[idx].hash := 0; + mEntries[idx].nextFree := @mEntries[idx+1]; //idx+1; + end; + mEntries[High(mEntries)].hash := 0; + mEntries[High(mEntries)].nextFree := nil; + } + { + if (mFirstEntry >= 0) then + begin + for idx := mFirstEntry to mLastEntry do + begin + mEntries[idx].hash := 0; + mEntries[idx].nextFree := nil; + end; + end; + } + + mBucketsUsed := 0; + {$IFDEF RBHASH_SANITY_CHECKS} + mEntriesUsed := 0; + {$ENDIF} + mFreeEntryHead := nil; //@mEntries[0]; + mFirstEntry := -1; + mLastEntry := -1; + end; +end; + + function THashBase.getCapacity (): Integer; inline; begin result := Length(mBuckets); end; @@ -329,6 +434,22 @@ function THashBase.allocEntry (): PEntry; var idx: Integer; begin + if (mFreeEntryHead = nil) then + begin + if (mLastEntry = High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (0.0)'); + Inc(mLastEntry); + if (mFirstEntry = -1) then + begin + if (mLastEntry <> 0) then raise Exception.Create('internal error in hash entry allocator (0.1)'); + mFirstEntry := 0; + end; + result := @mEntries[mLastEntry]; + result.nextFree := nil; // just in case + {$IFDEF RBHASH_SANITY_CHECKS} + Inc(mEntriesUsed); + {$ENDIF} + exit; + end; {$IFDEF RBHASH_SANITY_CHECKS} if (mFreeEntryHead = nil) then raise Exception.Create('internal error in hash entry allocator (0)'); if (mFreeEntryHead.hash <> 0) then raise Exception.Create('internal error in hash entry allocator (1)'); @@ -365,12 +486,12 @@ begin if (idx < 0) or (idx > High(mEntries)) then raise Exception.Create('internal error in hash entry allocator (invalid entry address)'); if (e <> @mEntries[idx]) then raise Exception.Create('internal error in hash entry allocator (wtf?!)'); {$ENDIF} - e.hash := 0; - e.nextFree := mFreeEntryHead; - mFreeEntryHead := e; //idx; {$IFDEF RBHASH_SANITY_CHECKS} Dec(mEntriesUsed); {$ENDIF} + e.hash := 0; + e.nextFree := mFreeEntryHead; + mFreeEntryHead := e; //idx; // fix mFirstEntry and mLastEntry {$IFDEF RBHASH_SANITY_CHECKS} if (mFirstEntry < 0) or (mLastEntry < 0) then raise Exception.Create('internal error in hash entry allocator (invalid first/last range; 0)'); @@ -797,4 +918,105 @@ begin end; +// enumerators +function THashBase.GetEnumerator (): TValEnumerator; +begin + if (Length(mEntries) > 0) then result := TValEnumerator.Create(mEntries, mFirstEntry, mLastEntry) + else result := TValEnumerator.Create(nil, -1, -1); +end; + +function THashBase.byKey (): TKeyEnumerator; +begin + if (Length(mEntries) > 0) then result := TKeyEnumerator.Create(mEntries, mFirstEntry, mLastEntry) + else result := TKeyEnumerator.Create(nil, -1, -1); +end; + +function THashBase.byValue (): TValEnumerator; +begin + if (Length(mEntries) > 0) then result := TValEnumerator.Create(mEntries, mFirstEntry, mLastEntry) + else result := TValEnumerator.Create(nil, -1, -1); +end; + +function THashBase.byKeyValue (): TKeyValEnumerator; // PEntry +begin + if (Length(mEntries) > 0) then result := TKeyValEnumerator.Create(mEntries, mFirstEntry, mLastEntry) + else result := TKeyValEnumerator.Create(nil, -1, -1); +end; + + +// ////////////////////////////////////////////////////////////////////////// // +constructor THashBase.TValEnumerator.Create (const aents: TEntryArray; afirst, alast: Integer); +begin + mEntries := aents; + mFirstEntry := afirst; + mLastEntry := alast; + cur := mFirstEntry-1; +end; + +function THashBase.TValEnumerator.MoveNext (): Boolean; inline; +begin + Inc(cur); + while (cur <= mLastEntry) do + begin + if (mEntries[cur].hash <> 0) then begin result := true; exit; end; + end; + result := false; +end; + +function THashBase.TValEnumerator.getCurrent (): ValueT; inline; +begin + result := mEntries[cur].value; +end; + + +// ////////////////////////////////////////////////////////////////////////// // +constructor THashBase.TKeyEnumerator.Create (const aents: TEntryArray; afirst, alast: Integer); +begin + mEntries := aents; + mFirstEntry := afirst; + mLastEntry := alast; + cur := mFirstEntry-1; +end; + +function THashBase.TKeyEnumerator.MoveNext (): Boolean; inline; +begin + Inc(cur); + while (cur <= mLastEntry) do + begin + if (mEntries[cur].hash <> 0) then begin result := true; exit; end; + end; + result := false; +end; + +function THashBase.TKeyEnumerator.getCurrent (): KeyT; inline; +begin + result := mEntries[cur].key; +end; + + +// ////////////////////////////////////////////////////////////////////////// // +constructor THashBase.TKeyValEnumerator.Create (const aents: TEntryArray; afirst, alast: Integer); +begin + mEntries := aents; + mFirstEntry := afirst; + mLastEntry := alast; + cur := mFirstEntry-1; +end; + +function THashBase.TKeyValEnumerator.MoveNext (): Boolean; inline; +begin + Inc(cur); + while (cur <= mLastEntry) do + begin + if (mEntries[cur].hash <> 0) then begin result := true; exit; end; + end; + result := false; +end; + +function THashBase.TKeyValEnumerator.getCurrent (): PEntry; inline; +begin + result := @mEntries[cur]; +end; + + end.