{ Copyright (c) 1989 by Borland International, Inc. } unit TCHash; { Turbo Pascal 5.5 object-oriented example hash tables. This unit is used by TCALC.PAS. See TCALC.DOC for an more information about this example. } {$S-} interface uses TCUtil; { This unit allows you to implement hash tables. Each hash table is composed of a number of "buckets", each of which points to a linked list of data entries. The bucket that a particular data entry goes into is determined by the HashValue function. } const MaxBuckets = 1000; MaxHashItemSize = 256; type BucketRange = 1..MaxBuckets; HashItemSizeRange = 1..MaxHashItemSize; HashItemData = array[0..Pred(MaxHashItemSize)] of Byte; HashItemDataPtr = ^HashItemData; HashItemPtr = ^HashItem; HashItem = record Next : HashItemPtr; Data : HashItemData; end; HashItemArray = array[BucketRange] of HashItemPtr; HashTable = object Buckets : BucketRange; Items : Longint; CurrItem : HashItemPtr; CurrBucket : BucketRange; HashData : ^HashItemArray; constructor Init(InitBuckets : BucketRange); destructor Done; function Add : Boolean; procedure Delete(Deleted : Pointer); function FirstItem : HashItemPtr; function NextItem : HashItemPtr; function Change : Boolean; function Search : HashItemPtr; function HashValue : Word; virtual; function Found(Item : HashItemPtr) : Boolean; virtual; procedure CreateItem(var Item : HashItemPtr); virtual; function ItemSize : HashItemSizeRange; virtual; function CurrItemSize(Item : HashItemPtr) : HashItemSizeRange; virtual; end; implementation constructor HashTable.Init(InitBuckets : BucketRange); { Initialize a new hash table with a certain number of buckets } begin GetMem(HashData, InitBuckets * SizeOf(HashItemPtr)); if HashData = nil then Fail; Buckets := InitBuckets; FillChar(HashData^, Buckets * SizeOf(HashItemPtr), 0); Items := 0; end; { HashTable.Init } destructor HashTable.Done; { Removes a hash table from memory } var P, D : HashItemPtr; Counter : Word; begin for Counter := 1 to Buckets do begin P := HashData^[Counter]; while P <> nil do begin D := P; P := P^.Next; FreeMem(D, CurrItemSize(D) + SizeOf(HashItemPtr)); end; end; FreeMem(HashData, Buckets * SizeOf(HashItemPtr)); end; { HashTable.Done } function HashTable.Add : Boolean; { Adds a new item to a hash table } var H, A : HashItemPtr; V : BucketRange; begin Add := False; V := Succ(HashValue mod Buckets); H := HashData^[V]; A := H; while H <> nil do begin H := H^.Next; if H <> nil then A := H; end; if A = nil then { Item will be the first element in the list } begin GetMem(HashData^[V], ItemSize + SizeOf(HashItemPtr)); A := HashData^[V]; if A = nil then Exit; end else begin { Add item and end of list } GetMem(A^.Next, ItemSize + SizeOf(HashItemPtr)); if A^.Next = nil then Exit; A := A^.Next; end; CreateItem(A); A^.Next := nil; Inc(Items); Add := True; end; { HashTable.Add } procedure HashTable.Delete(Deleted : Pointer); { Deletes an item from a hash table, and returns the deleted item } var H, D : HashItemPtr; V : BucketRange; begin V := Succ(HashValue mod Buckets); H := HashData^[V]; D := H; while (H <> nil) and (not Found(H)) do begin H := H^.Next; if not Found(H) then D := H; end; if H = nil then { The item was not found } begin if Deleted <> nil then FillChar(Deleted^, ItemSize, 0); Exit; end else begin if H = HashData^[V] then HashData^[V] := HashData^[V]^.Next else D^.Next := H^.Next; if Deleted <> nil then { Fill Deleted with the item's data } Move(H^.Data, Deleted^, ItemSize); FreeMem(H, CurrItemSize(H) + SizeOf(HashItemPtr)); end; Dec(Items); end; { HashTable.Delete } function HashTable.FirstItem : HashItemPtr; { Returns the first item in a hash table. to find all of the items in a hash table, call FirstItem to get the first one and then call NextItem to get the rest } var Counter : Word; begin for Counter := 1 to Buckets do begin CurrBucket := Counter; CurrItem := HashData^[Counter]; if CurrItem <> nil then begin FirstItem := CurrItem; Exit; end; end; FirstItem := nil; end; { HashTable.FirstItem } function HashTable.NextItem : HashItemPtr; { Returns the next item in a hash table - called after FirstItem } begin CurrItem := CurrItem^.Next; if CurrItem <> nil then begin NextItem := CurrItem; Exit; end; while CurrBucket < Buckets do begin Inc(CurrBucket); CurrItem := HashData^[CurrBucket]; if CurrItem <> nil then begin NextItem := CurrItem; Exit; end; end; NextItem := nil; end; { HashTable.NextItem } function HashTable.Change : Boolean; { Changes the data of a hash item } var H : HashItemPtr; begin H := HashData^[Succ(HashValue mod Buckets)]; while (H <> nil) and (not Found(H)) do H := H^.Next; if H <> nil then begin CreateItem(H); Change := True; end else Change := Add; end; { HashTable.Change } function HashTable.Search : HashItemPtr; { Searches for a particular hash item } var H : HashItemPtr; begin H := HashData^[Succ(HashValue mod Buckets)]; while (H <> nil) and (not Found(H)) do H := H^.Next; Search := H; end; { HashTable.Search } function HashTable.HashValue : Word; { Returns a hash value - must be written by the user } begin Abstract('HashTable.HashValue'); end; { HashTable.HashValue } function HashTable.Found(Item : HashItemPtr) : Boolean; { Returns a boolean value indicating whether the current hash item is the one being searched for - must be written by the user } begin Abstract('HashTable.Found'); end; { HashTable.Found } procedure HashTable.CreateItem(var Item : HashItemPtr); { Creates a hash item - must be written by the user } begin Abstract('HashTable.CreateItem'); end; { HashTable.CreateItem } function HashTable.ItemSize : HashItemSizeRange; { Returns the size of a hash item. If the hash item size is variable, this is based on whatever the item being searched for, added, or deleted is - must be written by the user } begin Abstract('HashTable.ItemSize'); end; { HashTable.ItemSize } function HashTable.CurrItemSize(Item : HashItemPtr) : HashItemSizeRange; { Returns the size of a particular item. This needs to be written only if the size of hash items is variable (strings, etc.) } begin CurrItemSize := ItemSize; end; { HashTable.CurrItemSize } end.