(*:Preallocated hasher. @author Primoz Gabrijelcic @desc
Copyright (c) 2008, Primoz Gabrijelcic
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.
- The name of the Primoz Gabrijelcic may not be used to endorse or promote
  products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

  Tested with Delphi 2007. Should work with older versions, too.

   Author            : Primoz Gabrijelcic
   Creation date     : 2005-02-24
   Last modification : 2008-10-04
   Version           : 1.04
*)(* History: 1.04: 2008-10-20 - Added TGpStringTable string storage. - Added TGpStringDictionary - a hash that uses TGpStringTable for string storage. 1.03: 2008-10-04 - Added support for growing. 1.02: 2007-12-06 - Much enhanced TGpStringHash. - TGpStringObjectHash class added. 1.01: 2006-04-13 - Added simplified constructor. *) unit GpStringHash; interface {$IFDEF CONDITIONALEXPRESSIONS} {$IF (CompilerVersion >= 17)} //Delphi 2005 or newer {$DEFINE GpStringHash_Inline} {$DEFINE GpStringHash_Enumerators} {$IFEND} {$ENDIF} type PGpHashItem = ^TGpHashItem; TGpHashItem = record Next : cardinal; Key : string; Value: integer; end; { TGpHashItem } {$IFDEF GpStringHash_Enumerators} TGpStringHash = class; TGpStringHashEnumerator = class private sheIndex: cardinal; sheHash : TGpStringHash; public constructor Create(stringHash: TGpStringHash); function GetCurrent: integer; {$IFDEF GpLists_Inline}inline;{$ENDIF} function MoveNext: boolean; {$IFDEF GpLists_Inline}inline;{$ENDIF} property Current: integer read GetCurrent; end; { TGpStringHashEnumerator } {$ENDIF GpStringHash_Enumerators} ///String-indexed hash of integer items. ///2005-02-24 TGpStringHash = class private shBuckets : array of cardinal; shCanGrow : boolean; shFirstEmpty: cardinal; shItems : array of TGpHashItem; shNumBuckets: cardinal; shSize : cardinal; protected function FindBucket(const key: string): cardinal; function GetHashItem(idxHashItem: cardinal): PGpHashItem; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} function GetItems(const key: string): integer; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} procedure Grow; procedure SetItems(const key: string; const value: integer); {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} property HashItems[idxItem: cardinal]: PGpHashItem read GetHashItem; public constructor Create(numItems: cardinal; canGrow: boolean = false); overload; constructor Create(numBuckets, numItems: cardinal; canGrow: boolean = false); overload; destructor Destroy; override; procedure Add(const key: string; value: integer); function Find(const key: string; var value: integer): boolean; {$IFDEF GpStringHash_Enumerators} function GetEnumerator: TGpStringHashEnumerator; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} {$ENDIF GpStringHash_Enumerators} function HasKey(const key: string): boolean; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} procedure Update(const key: string; value: integer); function ValueOf(const key: string): integer; property Items[const key: string]: integer read GetItems write SetItems; default; end; { TGpStringHash } {$IFDEF GpStringHash_Enumerators} TGpStringObjectHashEnumerator = class private soheStringEnumerator: TGpStringHashEnumerator; public constructor Create(stringHash: TGpStringHash); destructor Destroy; override; function GetCurrent: TObject; {$IFDEF GpLists_Inline}inline;{$ENDIF} function MoveNext: boolean; {$IFDEF GpLists_Inline}inline;{$ENDIF} property Current: TObject read GetCurrent; end; { TGpStringObjectHashEnumerator } {$ENDIF GpStringHash_Enumerators} ///String-indexed hash of objects. /// Redirects all operations to the internal TGpStringHash. ///2007-12-06 TGpStringObjectHash = class private sohHash : TGpStringHash; sohOwnsObjects: boolean; protected function GetObjects(const key: string): TObject; procedure SetObjects(const key: string; const value: TObject); {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} public constructor Create(numItems: cardinal; ownsObjects: boolean = true; canGrow: boolean = false); overload; constructor Create(numBuckets, numItems: cardinal; ownsObjects: boolean = true; canGrow: boolean = false); overload; destructor Destroy; override; procedure Add(const key: string; value: TObject); {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} function Find(const key: string; var value: TObject): boolean; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} {$IFDEF GpStringHash_Enumerators} function GetEnumerator: TGpStringObjectHashEnumerator; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} {$ENDIF GpStringHash_Enumerators} function HasKey(const key: string): boolean; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} procedure Update(const key: string; value: TObject); function ValueOf(const key: string): TObject; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} property Objects[const key: string]: TObject read GetObjects write SetObjects; default; end; { TGpStringObjectHash } {$IFDEF GpStringHash_Enumerators} TGpStringTable = class; TGpStringTableKV = class private kvKey : string; kvValue: int64; public property Key: string read kvKey write kvKey; property Value: int64 read kvValue write kvValue; end; { TGpStringTableKV } TGpStringTableEnumerator = class private steCurrent: TGpStringTableKV; steTable : PByte; steTail : pointer; public constructor Create(pTable, pTail: pointer); destructor Destroy; override; function GetCurrent: TGpStringTableKV; function MoveNext: boolean; property Current: TGpStringTableKV read GetCurrent; end; { TGpStringDictionaryEnumerator } {$ENDIF GpStringHash_Enumerators} ///A growable table of strings that doesn't support a Delete operation. /// When string is inserted, its position (relative to the table start) is never /// changed and can be used as a index. TGpStringDictionary uses this class for the /// underlying data structure. ///Data layout: [string length:32][string:N][value:8]. /// ///2008-10-20 TGpStringTable = class private stCanGrow : boolean; stData : pointer; stDataSize: cardinal; stDataTail: PByte; protected procedure CheckPointer(pData: pointer; dataSize: cardinal); function GetKey(index: cardinal): string; function GetValue(index: cardinal): int64; procedure Grow(requiredSize: cardinal); procedure SetValue(index: cardinal; const value: int64); public constructor Create(initialSize: cardinal; canGrow: boolean = true); destructor Destroy; override; function Add(const key: string; value: int64): cardinal; procedure Get(index: cardinal; var key: string; var value: int64); {$IFDEF GpStringHash_Enumerators} function GetEnumerator: TGpStringTableEnumerator; {$ENDIF GpStringHash_Enumerators} property Key[index: cardinal]: string read GetKey; property Value[index: cardinal]: int64 read GetValue write SetValue; end; { TGpStringTable } {$IFDEF GpStringHash_Enumerators} TGpStringDictionaryEnumerator = TGpStringTableEnumerator; {$ENDIF GpStringHash_Enumerators} PGpTableHashItem = ^TGpTableHashItem; TGpTableHashItem = record Next : cardinal; Index: cardinal; end; { TGpHashItem } ///A dictionary of pairs. ///2008-10-20 TGpStringDictionary = class private sdBuckets : array of cardinal; sdCanGrow : boolean; sdFirstEmpty : cardinal; sdItems : array of TGpTableHashItem; sdNumBuckets : cardinal; sdSize : cardinal; sdStringTable: TGpStringTable; protected function FindBucket(const key: string): cardinal; function GetHashItem(idxHashItem: cardinal): PGpTableHashItem;{$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} function GetItems(const key: string): int64; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} procedure Grow; procedure SetItems(const key: string; const value: int64); {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} property HashItems[idxItem: cardinal]: PGpTableHashItem read GetHashItem; public constructor Create(numItems, initialArraySize: cardinal); destructor Destroy; override; procedure Add(const key: string; value: int64); function Find(const key: string; var value: int64): boolean; {$IFDEF GpStringHash_Enumerators} function GetEnumerator: TGpStringDictionaryEnumerator; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} {$ENDIF GpStringHash_Enumerators} function HasKey(const key: string): boolean; {$IFDEF GpStringHash_Inline}inline;{$ENDIF GpStringHash_Inline} procedure Update(const key: string; value: int64); function ValueOf(const key: string): int64; property Items[const key: string]: int64 read GetItems write SetItems; default; end; { TGpStringDictionary } function GetGoodHashSize(dataSize: cardinal): cardinal; implementation uses SysUtils, DSiWin32; const //List of good hash table sizes, taken from //http://planetmath.org/encyclopedia/GoodHashTablePrimes.html CGpGoodHashSizes: array [5..30] of cardinal = (53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741); {$R-,Q-} function GetGoodHashSize(dataSize: cardinal): cardinal; var iHashSize: integer; upper : cardinal; begin upper := 1 SHL Low(CGpGoodHashSizes); for iHashSize := Low(CGpGoodHashSizes) to High(CGpGoodHashSizes) do begin Result := CGpGoodHashSizes[iHashSize]; if dataSize <= upper then Exit; upper := 2*upper; end; raise Exception.CreateFmt('GetGoodHashSize: Only data sizes up to %d are supported', [upper div 2]); end; { GetGoodHashSize } function HashOf(const key: string): cardinal; asm xor edx,edx { result := 0 } and eax,eax { test if 0 } jz @End { skip if nil } mov ecx,[eax-4] { ecx := string length } jecxz @End { skip if length = 0 } @loop: { repeat } rol edx,2 { edx := (edx shl 2) or (edx shr 30)... } xor dl,[eax] { ... xor Ord(key[eax]) } inc eax { inc(eax) } loop @loop { until ecx = 0 } @End: mov eax,edx { result := eax } end; { HashOf } {$IFDEF GpStringHash_Enumerators} { TGpStringHashEnumerator } constructor TGpStringHashEnumerator.Create(stringHash: TGpStringHash); begin sheIndex := 0; sheHash := stringHash; end; { TGpStringHashEnumerator.Create } function TGpStringHashEnumerator.GetCurrent: integer; begin Result := sheHash.shItems[sheIndex].Value; end; { TGpStringHashEnumerator.GetCurrent } function TGpStringHashEnumerator.MoveNext: boolean; begin Result := sheIndex < (sheHash.shFirstEmpty - 1); if Result then Inc(sheIndex); end; { TGpStringHashEnumerator.MoveNext } {$ENDIF GpStringHash_Enumerators} { TGpStringHash } constructor TGpStringHash.Create(numItems: cardinal; canGrow: boolean); begin Create(GetGoodHashSize(numItems), numItems, canGrow); end; { TGpStringHash.Create } constructor TGpStringHash.Create(numBuckets, numItems: cardinal; canGrow: boolean); begin inherited Create; SetLength(shBuckets, numBuckets); SetLength(shItems, numItems + 1); shItems[0].Value := -1; // sentinel for the ValueOf operation shSize := numItems; shNumBuckets := numBuckets; shFirstEmpty := 1; shCanGrow := canGrow; end; { TGpStringHash.Create } destructor TGpStringHash.Destroy; begin SetLength(shItems, 0); SetLength(shBuckets, 0); inherited Destroy; end; { TGpStringHash.Destroy } procedure TGpStringHash.Add(const key: string; value: integer); var bucket: PGpHashItem; hash : cardinal; begin hash := HashOf(key) mod shNumBuckets; if shFirstEmpty > shSize then if shCanGrow then Grow else raise Exception.Create('TGpStringHash.Add: Maximum size reached'); bucket := @(shItems[shFirstEmpty]); // point to an empty slot in the pre-allocated array bucket^.Key := key; bucket^.Value := value; bucket^.Next := shBuckets[hash]; shBuckets[hash] := shFirstEmpty; Inc(shFirstEmpty); end; { TGpStringHash.Add } function TGpStringHash.Find(const key: string; var value: integer): boolean; var bucket: integer; begin bucket := FindBucket(key); if bucket > 0 then begin value := shItems[bucket].Value; Result := true; end else Result := false; end; { TGpStringHash.Find } {$IFDEF GpStringHash_Enumerators} function TGpStringHash.GetEnumerator: TGpStringHashEnumerator; begin Result := TGpStringHashEnumerator.Create(Self); end; { TGpStringHash.GetEnumerator } {$ENDIF GpStringHash_Enumerators} function TGpStringHash.FindBucket(const key: string): cardinal; begin Result := shBuckets[HashOf(key) mod shNumBuckets]; while (Result <> 0) and (shItems[Result].Key <> key) do Result := shItems[Result].Next; end; { TGpStringHash.FindBucket } function TGpStringHash.GetHashItem(idxHashItem: cardinal): PGpHashItem; begin if idxHashItem > 0 then Result := @shItems[idxHashItem] else Result := nil; end; { TGpStringHash.GetHashItem } function TGpStringHash.GetItems(const key: string): integer; begin Result := ValueOf(key); end; { TGpStringHash.GetItems } procedure TGpStringHash.Grow; var bucket : PGpHashItem; hash : cardinal; oldBucket : PGpHashItem; oldIndex : integer; shOldBuckets: array of cardinal; shOldItems : array of TGpHashItem; begin SetLength(shOldBuckets, Length(shBuckets)); Move(shBuckets[0], shOldBuckets[0], Length(shBuckets) * SizeOf(shBuckets[0])); SetLength(shOldItems, Length(shItems)); for oldIndex := 0 to Length(shItems) - 1 do begin shOldItems[oldIndex] := shItems[oldIndex]; shItems[oldIndex].Key := ''; shItems[oldIndex].Next := 0; end; SetLength(shItems, 2*Length(shItems) + 1); SetLength(shBuckets, GetGoodHashSize(Length(shItems))); FillChar(shBuckets[0], Length(shBuckets) * SizeOf(shBuckets[0]), 0); shItems[0].Value := -1; // sentinel for the ValueOf operation shSize := Length(shItems); shNumBuckets := Length(shBuckets); shFirstEmpty := 1; for oldIndex := 1 to Length(shOldItems) - 1 do begin oldBucket := @(shOldItems[oldIndex]); hash := HashOf(oldBucket.Key) mod shNumBuckets; bucket := @(shItems[shFirstEmpty]); // point to an empty slot in the pre-allocated array Move(oldBucket^, bucket^, SizeOf(bucket^) - SizeOf(bucket^.Next)); bucket^.Next := shBuckets[hash]; shBuckets[hash] := shFirstEmpty; Inc(shFirstEmpty); end; FillChar(shOldItems[0], Length(shOldItems) * SizeOf(shOldItems[0]), 0); //prevent string refcount problems end; { TGpStringHash.Grow } function TGpStringHash.HasKey(const key: string): boolean; begin Result := (FindBucket(key) <> 0); end; { TGpStringHash.HasKey } procedure TGpStringHash.SetItems(const key: string; const value: integer); begin Update(key, value); end; { TGpStringHash.SetItems } procedure TGpStringHash.Update(const key: string; value: integer); var bucket: integer; begin bucket := FindBucket(key); if bucket > 0 then shItems[bucket].Value := value else Add(key, value); end; { TGpStringHash.Update } function TGpStringHash.ValueOf(const key: string): integer; var bucket: integer; begin bucket := FindBucket(key); if bucket > 0 then Result := shItems[bucket].Value else raise Exception.CreateFmt('TGpStringHash.ValueOf: Key %s does not exist', [key]); end; { TGpStringHash.ValueOf } { TGpStringObjectHashEnumerator } constructor TGpStringObjectHashEnumerator.Create(stringHash: TGpStringHash); begin soheStringEnumerator := TGpStringHashEnumerator.Create(stringHash); end; { TGpStringObjectHashEnumerator.Create } destructor TGpStringObjectHashEnumerator.Destroy; begin FreeAndNil(soheStringEnumerator); inherited; end; { TGpStringObjectHashEnumerator.Destroy } function TGpStringObjectHashEnumerator.GetCurrent: TObject; begin Result := TObject(soheStringEnumerator.GetCurrent); end; { TGpStringObjectHashEnumerator.GetCurrent } function TGpStringObjectHashEnumerator.MoveNext: boolean; begin Result := soheStringEnumerator.MoveNext; end; { TGpStringObjectHashEnumerator.MoveNext } { TGpStringObjectHash } constructor TGpStringObjectHash.Create(numItems: cardinal; ownsObjects, canGrow: boolean); begin inherited Create; sohOwnsObjects := ownsObjects; sohHash := TGpStringHash.Create(numItems, canGrow); end; { TGpStringObjectHash.Create } constructor TGpStringObjectHash.Create(numBuckets, numItems: cardinal; ownsObjects, canGrow: boolean); begin inherited Create; sohOwnsObjects := ownsObjects; sohHash := TGpStringHash.Create(numBuckets, numItems, canGrow); end; { TGpStringObjectHash.Create } destructor TGpStringObjectHash.Destroy; var obj: TObject; begin if sohOwnsObjects and assigned(sohHash) then for obj in Self do obj.Free; FreeAndNil(sohHash); inherited; end; { TGpStringObjectHash.Destroy } procedure TGpStringObjectHash.Add(const key: string; value: TObject); begin sohHash.Add(key, integer(value)); end; { TGpStringObjectHash.Add } function TGpStringObjectHash.Find(const key: string; var value: TObject): boolean; begin Result := sohHash.Find(key, integer(value)); end; { TGpStringObjectHash.Find } {$IFDEF GpStringHash_Enumerators} function TGpStringObjectHash.GetEnumerator: TGpStringObjectHashEnumerator; begin Result := TGpStringObjectHashEnumerator.Create(sohHash); end; { TGpStringObjectHash.GetEnumerator } {$ENDIF GpStringHash_Enumerators} function TGpStringObjectHash.GetObjects(const key: string): TObject; var value: integer; begin if sohHash.Find(key, value) then Result := TObject(value) else Result := nil; end; { TGpStringObjectHash.GetObjects } function TGpStringObjectHash.HasKey(const key: string): boolean; begin Result := sohHash.HasKey(key); end; { TGpStringObjectHash.HasKey } procedure TGpStringObjectHash.SetObjects(const key: string; const value: TObject); begin Update(key, value); end; { TGpStringObjectHash.SetObjects } procedure TGpStringObjectHash.Update(const key: string; value: TObject); var bucket: cardinal; item : PGpHashItem; begin if not sohOwnsObjects then sohHash.Update(key, integer(value)) else begin bucket := sohHash.FindBucket(key); if bucket > 0 then begin item := sohHash.HashItems[bucket]; if TObject(item.Value) <> value then TObject(item.Value).Free; item.Value := integer(value); end else sohHash.Add(key, integer(value)); end; end; { TGpStringObjectHash.Update } function TGpStringObjectHash.ValueOf(const key: string): TObject; begin Result := TObject(sohHash.ValueOf(key)); end; { TGpStringObjectHash.ValueOf } {$IFDEF GpStringHash_Enumerators} { TGpStringTableEnumerator } constructor TGpStringTableEnumerator.Create(pTable, pTail: pointer); begin inherited Create; steCurrent := TGpStringTableKV.Create; steTable := pTable; steTail := pTail; end; { TGpStringTableEnumerator.Create } destructor TGpStringTableEnumerator.Destroy; begin FreeAndNil(steCurrent); inherited; end; { TGpStringTableEnumerator.Destroy } function TGpStringTableEnumerator.GetCurrent: TGpStringTableKV; var lenStr: cardinal; begin lenStr := PCardinal(steTable)^; SetLength(steCurrent.kvKey, lenStr); Inc(steTable, SizeOf(cardinal)); if Length(steCurrent.kvKey) > 0 then begin Move(steTable^, steCurrent.kvKey[1], lenStr*SizeOf(char)); Inc(steTable, lenStr*SizeOf(char)); end; steCurrent.kvValue := PInt64(steTable)^; Inc(steTable, SizeOf(int64)); Result := steCurrent; end; { TGpStringTableEnumerator.GetCurrent } function TGpStringTableEnumerator.MoveNext: boolean; begin Result := cardinal(steTable) < cardinal(steTail); end; { TGpStringTableEnumerator.MoveNext } {$ENDIF GpStringHash_Enumerators} { TGpStringTable } constructor TGpStringTable.Create(initialSize: cardinal; canGrow: boolean); begin inherited Create; GetMem(stData, initialSize); stDataSize := initialSize; stDataTail := stData; stCanGrow := canGrow; end; { TGpStringTable.Create } destructor TGpStringTable.Destroy; begin DSiFreeMemAndNil(stData); inherited; end; { TGpStringTable.Destroy } function TGpStringTable.Add(const key: string; value: int64): cardinal; var requiredSize: cardinal; begin if key = '' then raise Exception.Create('TGpStringTable.Add: Cannot store empty key'); Result := cardinal(stDataTail) - cardinal(stData); requiredSize := Result + SizeOf(cardinal) + cardinal(Length(key)*SizeOf(char)) + SizeOf(int64); if requiredSize > stDataSize then Grow(requiredSize); PCardinal(stDataTail)^ := Length(key); Inc(stDataTail, SizeOf(cardinal)); Move(key[1], stDataTail^, Length(key)*SizeOf(char)); Inc(stDataTail, Length(key)*SizeOf(char)); PInt64(stDataTail)^ := value; Inc(stDataTail, SizeOf(int64)); end; { TGpStringTable.Add } procedure TGpStringTable.CheckPointer(pData: pointer; dataSize: cardinal); begin if (cardinal(pData) + dataSize - cardinal(stData)) > stDataSize then raise Exception.Create('TGpStringTable: Invalid index'); end; { TGpStringTable.CheckPointer } procedure TGpStringTable.Grow(requiredSize: cardinal); var pNewData: pointer; begin if not stCanGrow then raise Exception.Create('TGpStringTable.Grow: String table size is fixed'); requiredSize := Round(requiredSize * 1.6); GetMem(pNewData, requiredSize); Move(stData^, pNewData^, stDataSize); stDataSize := requiredSize; stDataTail := PByte(cardinal(stDataTail) - cardinal(stData) + cardinal(pNewData)); FreeMem(stData); stData := pNewData; end; { TGpStringTable.Grow } procedure TGpStringTable.Get(index: cardinal; var key: string; var value: int64); var lenStr: cardinal; pData : PByte; begin pData := pointer(cardinal(stData) + index); CheckPointer(pData, SizeOf(cardinal)); lenStr := PCardinal(pData)^; Inc(pData, SizeOf(cardinal)); CheckPointer(pData, lenStr); SetLength(key, lenStr); if lenStr > 0 then begin Move(pData^, key[1], lenStr*SizeOf(char)); Inc(pData, lenStr*SizeOf(char)); end; CheckPointer(pData, SizeOf(int64)); value := PInt64(pData)^; end; { TGpStringTable.Get } {$IFDEF GpStringHash_Enumerators} function TGpStringTable.GetEnumerator: TGpStringTableEnumerator; begin Result := TGpStringTableEnumerator.Create(stData, stDataTail); end; { TGpStringTable.GetEnumerator } {$ENDIF GpStringHash_Enumerators} function TGpStringTable.GetKey(index: cardinal): string; var lenStr: cardinal; pData : PByte; begin pData := pointer(cardinal(stData) + index); CheckPointer(pData, SizeOf(cardinal)); lenStr := PCardinal(pData)^; Inc(pData, SizeOf(cardinal)); CheckPointer(pData, lenStr); SetLength(Result, lenStr); if lenStr > 0 then Move(pData^, Result[1], lenStr*SizeOf(char)); end; { TGpStringTable.GetKey } function TGpStringTable.GetValue(index: cardinal): int64; var lenStr: cardinal; pData : PByte; begin pData := pointer(cardinal(stData) + index); CheckPointer(pData, SizeOf(cardinal)); lenStr := PCardinal(pData)^; Inc(pData, SizeOf(cardinal)); CheckPointer(pData, lenStr); Inc(pData, lenStr); CheckPointer(pData, SizeOf(int64)); Result := PInt64(pData)^; end; { TGpStringTable.GetValue } procedure TGpStringTable.SetValue(index: cardinal; const value: int64); var pData: PByte; begin pData := pointer(cardinal(stData) + index); CheckPointer(pData, SizeOf(cardinal)); Inc(pData, SizeOf(cardinal)); CheckPointer(pData, PCardinal(pData)^); Inc(pData, PCardinal(pData)^); CheckPointer(pData, SizeOf(int64)); PInt64(pData)^ := value; end; { TGpStringTable.SetValue } { TGpStringDictionary } constructor TGpStringDictionary.Create(numItems, initialArraySize: cardinal); begin inherited Create; sdStringTable := TGpStringTable.Create(initialArraySize); sdNumBuckets := GetGoodHashSize(numItems); SetLength(sdBuckets, sdNumBuckets); SetLength(sdItems, numItems + 1); sdSize := numItems; sdFirstEmpty := 1; sdCanGrow := true; end; { TGpStringDictionary.Create } destructor TGpStringDictionary.Destroy; begin SetLength(sdItems, 0); SetLength(sdBuckets, 0); FreeAndNil(sdStringTable); inherited Destroy; end; { TGpStringDictionary.Destroy } procedure TGpStringDictionary.Add(const key: string; value: int64); var bucket: PGpTableHashItem; hash : cardinal; begin hash := HashOf(key) mod sdNumBuckets; if sdFirstEmpty > sdSize then if sdCanGrow then Grow else raise Exception.Create('TGpStringDictionary.Add: Maximum size reached'); bucket := @(sdItems[sdFirstEmpty]); // point to an empty slot in the pre-allocated array bucket^.Index := sdStringTable.Add(key, value); bucket^.Next := sdBuckets[hash]; sdBuckets[hash] := sdFirstEmpty; Inc(sdFirstEmpty); end; { TGpStringDictionary.Add } function TGpStringDictionary.Find(const key: string; var value: int64): boolean; var bucket: integer; begin bucket := FindBucket(key); if bucket > 0 then begin value := sdStringTable.Value[sdItems[bucket].index]; Result := true; end else Result := false; end; { TGpStringDictionary.Find } function TGpStringDictionary.FindBucket(const key: string): cardinal; begin Result := sdBuckets[HashOf(key) mod sdNumBuckets]; while (Result <> 0) and (sdStringTable.Key[sdItems[Result].Index] <> key) do Result := sdItems[Result].Next; end; { TGpStringDictionary.FindBucket } {$IFDEF GpStringHash_Enumerators} function TGpStringDictionary.GetEnumerator: TGpStringDictionaryEnumerator; begin Result := sdStringTable.GetEnumerator; end; { TGpStringDictionary.GetEnumerator } {$ENDIF GpStringHash_Enumerators} function TGpStringDictionary.GetHashItem(idxHashItem: cardinal): PGpTableHashItem; begin if idxHashItem > 0 then Result := @sdItems[idxHashItem] else Result := nil; end; { TGpStringDictionary.GetHashItem } function TGpStringDictionary.GetItems(const key: string): int64; begin Result := ValueOf(key); end; { TGpStringDictionary.GetItems } procedure TGpStringDictionary.Grow; var bucket : PGpTableHashItem; hash : cardinal; oldBucket : PGpTableHashItem; oldIndex : integer; shOldBuckets: array of cardinal; shOldItems : array of TGpTableHashItem; begin SetLength(shOldBuckets, Length(sdBuckets)); Move(sdBuckets[0], shOldBuckets[0], Length(sdBuckets) * SizeOf(sdBuckets[0])); SetLength(shOldItems, Length(sdItems)); for oldIndex := 0 to Length(sdItems) - 1 do begin shOldItems[oldIndex] := sdItems[oldIndex]; sdItems[oldIndex].Next := 0; end; SetLength(sdItems, 2*Length(sdItems) + 1); SetLength(sdBuckets, GetGoodHashSize(Length(sdItems))); FillChar(sdBuckets[0], Length(sdBuckets) * SizeOf(sdBuckets[0]), 0); sdSize := Length(sdItems); sdNumBuckets := Length(sdBuckets); sdFirstEmpty := 1; for oldIndex := 1 to Length(shOldItems) - 1 do begin oldBucket := @(shOldItems[oldIndex]); hash := HashOf(sdStringTable.Key[oldBucket.index]) mod sdNumBuckets; bucket := @(sdItems[sdFirstEmpty]); // point to an empty slot in the pre-allocated array Move(oldBucket^, bucket^, SizeOf(bucket^) - SizeOf(bucket^.Next)); bucket^.Next := sdBuckets[hash]; sdBuckets[hash] := sdFirstEmpty; Inc(sdFirstEmpty); end; FillChar(shOldItems[0], Length(shOldItems) * SizeOf(shOldItems[0]), 0); //prevent string refcount problems end; { TGpStringDictionary.Grow } function TGpStringDictionary.HasKey(const key: string): boolean; begin Result := (FindBucket(key) <> 0); end; { TGpStringDictionary.HasKey } procedure TGpStringDictionary.SetItems(const key: string; const value: int64); begin Update(key, value); end; { TGpStringDictionary.SetItems } procedure TGpStringDictionary.Update(const key: string; value: int64); var bucket: integer; begin bucket := FindBucket(key); if bucket > 0 then sdStringTable.Value[sdItems[bucket].Index] := value else Add(key, value); end; { TGpStringDictionary.Update } function TGpStringDictionary.ValueOf(const key: string): int64; var bucket: integer; begin bucket := FindBucket(key); if bucket > 0 then Result := sdStringTable.Value[sdItems[bucket].Index] else raise Exception.CreateFmt('TGpStringDictionary.ValueOf: Key %s does not exist', [key]); end; { TGpStringDictionary.ValueOf } end.