mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-12 18:09:55 +02:00
950 lines
26 KiB
ObjectPascal
950 lines
26 KiB
ObjectPascal
{
|
|
Author: Mattias Gaertner
|
|
|
|
*****************************************************************************
|
|
* *
|
|
* This file is part of the Lazarus Component Library (LCL) *
|
|
* *
|
|
* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
|
|
* for details about the copyright. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* *
|
|
*****************************************************************************
|
|
|
|
Abstract:
|
|
This unit defines TDynHashArray, which is very similar to a TList, since
|
|
it also stores pointer/objects.
|
|
It supports Add, Remove, Contains, First, Count and Clear.
|
|
Because of the hashing nature the operations adding, removing and finding is
|
|
done in constant time on average.
|
|
|
|
Inner structure:
|
|
There are three parts:
|
|
1. The array itself (FItems). Every entry is a pointer to the first
|
|
TDynHashArrayItem of a list with the same hash index. The first item
|
|
of every same index list is the list beginning and its IsOverflow
|
|
flag is set to false. All other items are overflow items.
|
|
To get all items with the same hash index, do a FindHashItem. Then
|
|
search through all "Next" items until Next is nil or its IsOverflow
|
|
flag is set to false.
|
|
2. The items beginning with FFirstItem is a 2-way-connected list of
|
|
TDynHashArrayItem. This list contains all used items.
|
|
3. To reduce GetMem/FreeMem calls, free items are cached.
|
|
|
|
Issues:
|
|
The maximum capacity is the PrimeNumber. You can store more items, but the
|
|
performance decreases. The best idea is to provide your own hash function.
|
|
|
|
Important: Items in the TDynHashArray must not change their key.
|
|
When changing the key of an item, remove it and add it after the change.
|
|
|
|
}
|
|
unit DynHashArray;
|
|
|
|
{$Mode ObjFPC}{$H+}
|
|
|
|
interface
|
|
|
|
uses Classes, SysUtils, LCLProc;
|
|
|
|
type
|
|
TDynHashArray = class;
|
|
|
|
THashFunction = function(Sender: TDynHashArray; Item: Pointer): integer;
|
|
TOwnerHashFunction = function(Item: Pointer): integer of object;
|
|
TOnGetKeyForHashItem = function(Item: pointer): pointer;
|
|
TOnEachHashItem = function(Sender: TDynHashArray; Item: Pointer): boolean;
|
|
|
|
PDynHashArrayItem = ^TDynHashArrayItem;
|
|
TDynHashArrayItem = record
|
|
Item: Pointer;
|
|
Next, Prior: PDynHashArrayItem;
|
|
IsOverflow: boolean;
|
|
end;
|
|
|
|
TDynHashArrayOption = (dhaoCachingEnabled, dhaoCacheContains);
|
|
TDynHashArrayOptions = set of TDynHashArrayOption;
|
|
|
|
{ TDynHashArray }
|
|
|
|
TDynHashArray = class
|
|
private
|
|
FItems: ^PDynHashArrayItem;
|
|
FCount: integer;
|
|
FCapacity: integer;
|
|
FMinCapacity: integer;
|
|
FMaxCapacity: integer;
|
|
FFirstItem: PDynHashArrayItem;
|
|
FHashCacheItem: Pointer;
|
|
FHashCacheIndex: integer;
|
|
FLowWaterMark: integer;
|
|
FHighWaterMark: integer;
|
|
FCustomHashFunction: THashFunction;
|
|
FOnGetKeyForHashItem: TOnGetKeyForHashItem;
|
|
FOptions: TDynHashArrayOptions;
|
|
FOwnerHashFunction: TOwnerHashFunction;
|
|
FContainsCache: TObject;
|
|
function NewHashItem: PDynHashArrayItem;
|
|
procedure DisposeHashItem(ADynHashArrayItem: PDynHashArrayItem);
|
|
procedure ComputeWaterMarks;
|
|
procedure SetCapacity(NewCapacity: integer);
|
|
procedure SetCustomHashFunction(const AValue: THashFunction);
|
|
procedure SetOnGetKeyForHashItem(const AValue: TOnGetKeyForHashItem);
|
|
procedure SetOptions(const AValue: TDynHashArrayOptions);
|
|
procedure SetOwnerHashFunction(const AValue: TOwnerHashFunction);
|
|
protected
|
|
procedure RebuildItems;
|
|
procedure SaveCacheItem(Item: Pointer; Index: integer);
|
|
public
|
|
constructor Create;
|
|
constructor Create(InitialMinCapacity: integer);
|
|
destructor Destroy; override;
|
|
procedure Add(Item: Pointer);
|
|
function Contains(Item: Pointer): boolean;
|
|
function ContainsKey(Key: Pointer): boolean;
|
|
procedure Remove(Item: Pointer);
|
|
procedure Clear;
|
|
procedure ClearCache;
|
|
function First: Pointer;
|
|
property Count: integer read fCount;
|
|
function IndexOf(AnItem: Pointer): integer;
|
|
function IndexOfKey(Key: Pointer): integer;
|
|
function FindHashItem(Item: Pointer): PDynHashArrayItem;
|
|
function FindHashItemWithKey(Key: Pointer): PDynHashArrayItem;
|
|
function FindItemWithKey(Key: Pointer): Pointer;
|
|
function GetHashItem(HashIndex: integer): PDynHashArrayItem;
|
|
procedure Delete(ADynHashArrayItem: PDynHashArrayItem);
|
|
procedure AssignTo(List: TList);
|
|
procedure AssignTo(List: TFPList);
|
|
procedure ForEach(const Func: TOnEachHashItem);
|
|
|
|
function SlowAlternativeHashMethod(Sender: TDynHashArray;
|
|
Item: Pointer): integer;
|
|
function ConsistencyCheck: integer;
|
|
procedure WriteDebugReport;
|
|
|
|
property FirstHashItem: PDynHashArrayItem read FFirstItem;
|
|
property MinCapacity: integer read FMinCapacity write FMinCapacity;
|
|
property MaxCapacity: integer read FMaxCapacity write FMaxCapacity;
|
|
property Capacity: integer read FCapacity;
|
|
property CustomHashFunction: THashFunction
|
|
read FCustomHashFunction write SetCustomHashFunction;
|
|
property OwnerHashFunction: TOwnerHashFunction
|
|
read FOwnerHashFunction write SetOwnerHashFunction;
|
|
property OnGetKeyForHashItem: TOnGetKeyForHashItem
|
|
read FOnGetKeyForHashItem write SetOnGetKeyForHashItem;
|
|
property Options: TDynHashArrayOptions read FOptions write SetOptions;
|
|
end;
|
|
|
|
TDynHashArrayItemMemManager = class
|
|
private
|
|
FFirstFree: PDynHashArrayItem;
|
|
FFreeCount: integer;
|
|
FCount: integer;
|
|
FMinFree: integer;
|
|
FMaxFreeRatio: integer;
|
|
procedure SetMaxFreeRatio(NewValue: integer);
|
|
procedure SetMinFree(NewValue: integer);
|
|
procedure DisposeFirstFreeItem;
|
|
public
|
|
procedure DisposeItem(ADynHashArrayItem: PDynHashArrayItem);
|
|
function NewItem: PDynHashArrayItem;
|
|
property MinimumFreeCount: integer read FMinFree write SetMinFree;
|
|
property MaximumFreeRatio: integer
|
|
read FMaxFreeRatio write SetMaxFreeRatio; // in one eighth steps
|
|
property Count: integer read FCount;
|
|
procedure Clear;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function ConsistencyCheck: integer;
|
|
procedure WriteDebugReport;
|
|
end;
|
|
|
|
EDynHashArrayException = class(Exception);
|
|
|
|
const
|
|
ItemMemManager: TDynHashArrayItemMemManager = nil;
|
|
|
|
implementation
|
|
|
|
function GetItemMemManager: TDynHashArrayItemMemManager;
|
|
begin
|
|
if ItemMemManager=nil then
|
|
ItemMemManager:=TDynHashArrayItemMemManager.Create;
|
|
Result:=ItemMemManager;
|
|
end;
|
|
|
|
const
|
|
PrimeNumber: integer = 5364329;
|
|
|
|
|
|
type
|
|
TRecentList = class
|
|
private
|
|
FCapacity: integer;
|
|
FCount: integer;
|
|
FItems: PPointer;
|
|
procedure FreeItems;
|
|
procedure SetCapacity(NewCapacity: integer);
|
|
public
|
|
constructor Create(TheCapacity: integer);
|
|
destructor Destroy; override;
|
|
function Contains(Item: Pointer): boolean;
|
|
procedure Add(Item: Pointer);
|
|
procedure Remove(Item: Pointer);
|
|
function IndexOf(Item: Pointer): integer;
|
|
procedure Clear;
|
|
function ConsistencyCheck: integer;
|
|
property Cacpacity: integer read FCapacity;
|
|
property Count: integer read FCount;
|
|
end;
|
|
|
|
{ TRecentList }
|
|
|
|
procedure TRecentList.FreeItems;
|
|
begin
|
|
if FItems<>nil then begin
|
|
FreeMem(FItems);
|
|
FItems:=nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TRecentList.SetCapacity(NewCapacity: integer);
|
|
begin
|
|
if NewCapacity=FCapacity then exit;
|
|
if NewCapacity>0 then
|
|
ReAllocMem(FItems,NewCapacity*SizeOf(Pointer))
|
|
else
|
|
FreeItems;
|
|
FCapacity:=NewCapacity;
|
|
if FCount>FCapacity then FCount:=FCapacity;
|
|
end;
|
|
|
|
constructor TRecentList.Create(TheCapacity: integer);
|
|
begin
|
|
inherited Create;
|
|
if TheCapacity<1 then FCapacity:=1;
|
|
SetCapacity(TheCapacity);
|
|
end;
|
|
|
|
destructor TRecentList.Destroy;
|
|
begin
|
|
FreeItems;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TRecentList.Contains(Item: Pointer): boolean;
|
|
begin
|
|
Result:=IndexOf(Item)>=0;
|
|
end;
|
|
|
|
procedure TRecentList.Add(Item: Pointer);
|
|
begin
|
|
if FCount=FCapacity then begin
|
|
if FCount>1 then
|
|
Move(FItems[1],FItems[0],SizeOf(PPointer)*(FCount-1));
|
|
end else begin
|
|
inc(FCount);
|
|
end;
|
|
FItems[FCount-1]:=Item;
|
|
end;
|
|
|
|
procedure TRecentList.Remove(Item: Pointer);
|
|
var i: integer;
|
|
begin
|
|
i:=IndexOf(Item);
|
|
if i<0 then exit;
|
|
if i<FCount-1 then
|
|
Move(FItems[i+1],FItems[i],SizeOf(PPointer)*(FCount-i-1));
|
|
dec(FCount);
|
|
end;
|
|
|
|
function TRecentList.IndexOf(Item: Pointer): integer;
|
|
begin
|
|
Result:=FCount-1;
|
|
while (Result>=0) and (FItems[Result]<>Item) do dec(Result);
|
|
end;
|
|
|
|
procedure TRecentList.Clear;
|
|
begin
|
|
FCount:=0;
|
|
end;
|
|
|
|
function TRecentList.ConsistencyCheck: integer;
|
|
begin
|
|
if FCount>FCapacity then exit(-1);
|
|
if FCapacity=0 then exit(-2);
|
|
if FItems=nil then exit(-3);
|
|
Result:=0;
|
|
end;
|
|
|
|
{ TDynHashArray }
|
|
|
|
procedure TDynHashArray.WriteDebugReport;
|
|
var i, RealHashIndex: integer;
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
DebugLn('TDynHashArray.WriteDebugReport: Consistency=',dbgs(ConsistencyCheck));
|
|
DebugLn(' Count=',dbgs(FCount),' Capacity=',dbgs(FCapacity));
|
|
for i:=0 to FCapacity-1 do begin
|
|
HashItem:=FItems[i];
|
|
if HashItem<>nil then begin
|
|
DbgOut(' Index=',IntToStr(i));
|
|
while HashItem<>nil do begin
|
|
DbgOut(' ',Dbgs(HashItem^.Item));
|
|
RealHashIndex:=IndexOf(HashItem^.Item);
|
|
if RealHashIndex<>i then DbgOut('(H='+dbgs(RealHashIndex)+')');
|
|
HashItem:=HashItem^.Next;
|
|
if (HashItem<>nil) and (HashItem^.IsOverflow=false) then break;
|
|
end;
|
|
DebugLn;
|
|
end;
|
|
end;
|
|
HashItem:=FFirstItem;
|
|
while HashItem<>nil do begin
|
|
DebugLn(' ',Dbgs(HashItem^.Prior),'<-'
|
|
,Dbgs(HashItem)
|
|
,'(',Dbgs(HashItem^.Item),')'
|
|
,'->',Dbgs(HashItem^.Next));
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
end;
|
|
|
|
constructor TDynHashArray.Create(InitialMinCapacity: integer);
|
|
var Size: integer;
|
|
begin
|
|
inherited Create;
|
|
FMinCapacity:=InitialMinCapacity;
|
|
FMaxCapacity:=PrimeNumber;
|
|
if FMinCapacity<5 then FMinCapacity:=137;
|
|
FCapacity:=FMinCapacity;
|
|
Size:=FCapacity * SizeOf(TDynHashArrayItem);
|
|
GetMem(FItems,Size);
|
|
FillChar(FItems^,Size,0);
|
|
FCount:=0;
|
|
FFirstItem:=nil;
|
|
ComputeWaterMarks;
|
|
FHashCacheIndex:=-1;
|
|
end;
|
|
|
|
destructor TDynHashArray.Destroy;
|
|
begin
|
|
Clear;
|
|
FreeMem(FItems);
|
|
FContainsCache.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TDynHashArray.ConsistencyCheck: integer;
|
|
var RealCount, i: integer;
|
|
HashItem, HashItem2: PDynHashArrayItem;
|
|
OldCacheItem: pointer;
|
|
OldCacheIndex: integer;
|
|
begin
|
|
RealCount:=0;
|
|
// check first item
|
|
if (FFirstItem<>nil) and (FFirstItem^.IsOverflow) then
|
|
exit(-1);
|
|
if (FItems=nil) and (FFirstItem<>nil) then
|
|
exit(-2);
|
|
// check for doubles and circles
|
|
HashItem:=FFirstItem;
|
|
while HashItem<>nil do begin
|
|
HashItem2:=HashItem^.Prior;
|
|
while HashItem2<>nil do begin
|
|
if HashItem=HashItem2 then
|
|
exit(-3); // circle
|
|
if HashItem^.Item=HashItem2^.Item then
|
|
exit(-4); // double item
|
|
HashItem2:=HashItem2^.Prior;
|
|
end;
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
// check chain
|
|
HashItem:=FFirstItem;
|
|
while HashItem<>nil do begin
|
|
inc(RealCount);
|
|
if (HashItem^.Next<>nil) and (HashItem^.Next^.Prior<>HashItem) then
|
|
exit(-6);
|
|
if (HashItem^.Prior<>nil) and (HashItem^.Prior^.Next<>HashItem) then
|
|
exit(-7);
|
|
if (HashItem^.IsOverflow=false)
|
|
and (FItems[IndexOf(HashItem^.Item)]<>HashItem) then
|
|
exit(-8);
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
// check count
|
|
if RealCount<>FCount then exit(-9);
|
|
// check FItems
|
|
RealCount:=0;
|
|
for i:=0 to FCapacity-1 do begin
|
|
HashItem:=FItems[i];
|
|
while HashItem<>nil do begin
|
|
inc(RealCount);
|
|
if IndexOf(HashItem^.Item)<>i then exit(-14);
|
|
HashItem:=HashItem^.Next;
|
|
if (HashItem<>nil) and (HashItem^.IsOverflow=false) then break;
|
|
end;
|
|
end;
|
|
if RealCount<>FCount then exit(-15);
|
|
// check cache
|
|
if FHashCacheIndex>=0 then begin
|
|
OldCacheItem:=FHashCacheItem;
|
|
OldCacheIndex:=FHashCacheIndex;
|
|
ClearCache;
|
|
FHashCacheIndex:=IndexOfKey(OldCacheItem);
|
|
if FHashCacheIndex<>OldCacheIndex then exit(-16);
|
|
FHashCacheItem:=OldCacheItem;
|
|
end;
|
|
// check ContainsCache
|
|
if (FContainsCache<>nil) xor (dhaoCacheContains in Options) then exit(-17);
|
|
if (FContainsCache<>nil) then begin
|
|
Result:=TRecentList(FContainsCache).ConsistencyCheck;
|
|
if Result<>0 then begin
|
|
dec(Result,100);
|
|
exit;
|
|
end;
|
|
end;
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TDynHashArray.ComputeWaterMarks;
|
|
begin
|
|
FLowWaterMark:=FCapacity div 4;
|
|
FHighWaterMark:=(FCapacity*3) div 4;
|
|
end;
|
|
|
|
function TDynHashArray.IndexOf(AnItem: Pointer): integer;
|
|
begin
|
|
if (AnItem<>nil) and (FItems<>nil) then begin
|
|
if Assigned(OnGetKeyForHashItem) then begin
|
|
AnItem:=OnGetKeyForHashItem(AnItem);
|
|
end;
|
|
Result:=IndexOfKey(AnItem);
|
|
end else
|
|
Result:=-1;
|
|
end;
|
|
|
|
function TDynHashArray.IndexOfKey(Key: Pointer): integer;
|
|
begin
|
|
if (FItems<>nil)
|
|
and ((Key<>nil) or Assigned(OnGetKeyForHashItem)) then begin
|
|
|
|
if (dhaoCachingEnabled in Options)
|
|
and (Key=FHashCacheItem) and (FHashCacheIndex>=0) then
|
|
exit(FHashCacheIndex);
|
|
if not Assigned(FCustomHashFunction) then begin
|
|
if not Assigned(FOwnerHashFunction) then begin
|
|
Result:=Integer((PtrUInt(Key)+(PtrUint(Key) mod 17)) mod Cardinal(FCapacity));
|
|
end else
|
|
Result:=FOwnerHashFunction(Key);
|
|
end else
|
|
Result:=FCustomHashFunction(Self,Key);
|
|
{if (Key=FHashCacheItem) and (FHashCacheIndex>=0)
|
|
and (Result<>FHashCacheIndex) then begin
|
|
DebugLn(' DAMN: ',HexStr(PtrInt(Key),8),' ',FHashCacheIndex,'<>',Result);
|
|
raise Exception.Create('GROSSER MIST');
|
|
end;}
|
|
// Check if the owner or custon function has returned something valid
|
|
if (Result < 0)
|
|
or (Result >= FCapacity)
|
|
then raise EDynHashArrayException.CreateFmt('Invalid index %d for key %p', [Result, Key]);
|
|
end else
|
|
Result:=-1;
|
|
end;
|
|
|
|
procedure TDynHashArray.Clear;
|
|
begin
|
|
ClearCache;
|
|
while FFirstItem<>nil do Delete(FFirstItem);
|
|
end;
|
|
|
|
procedure TDynHashArray.ClearCache;
|
|
begin
|
|
FHashCacheIndex:=-1;
|
|
if FContainsCache<>nil then TRecentList(FContainsCache).Clear;
|
|
end;
|
|
|
|
procedure TDynHashArray.Add(Item: Pointer);
|
|
var Index: integer;
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
if Item=nil then exit;
|
|
if FCount>=FHighWaterMark then begin
|
|
SetCapacity(FCapacity*2-1);
|
|
end;
|
|
Index:=IndexOf(Item);
|
|
if Index < 0 then Exit;
|
|
HashItem:=NewHashItem;
|
|
HashItem^.Item:=Item;
|
|
if FItems[Index]=nil then begin
|
|
HashItem^.Next:=FFirstItem;
|
|
end else begin
|
|
HashItem^.Next:=FItems[Index];
|
|
HashItem^.Prior:=HashItem^.Next^.Prior;
|
|
HashItem^.Next^.IsOverflow:=true;
|
|
end;
|
|
if (HashItem^.Next=FFirstItem) then
|
|
FFirstItem:=HashItem;
|
|
FItems[Index]:=HashItem;
|
|
if HashItem^.Next<>nil then begin
|
|
HashItem^.Next^.Prior:=HashItem;
|
|
if HashItem^.Prior<>nil then
|
|
HashItem^.Prior^.Next:=HashItem;
|
|
end;
|
|
inc(FCount);
|
|
SaveCacheItem(Item,Index);
|
|
if FContainsCache<>nil then TRecentList(FContainsCache).Clear;
|
|
end;
|
|
|
|
function TDynHashArray.SlowAlternativeHashMethod(Sender: TDynHashArray;
|
|
Item: Pointer): integer;
|
|
begin
|
|
Result:=integer((PtrUInt(Item) mod Cardinal(PrimeNumber))
|
|
+(PtrUInt(Item) mod 17)+(PtrUInt(Item) mod 173)
|
|
+(PtrUInt(Item) mod 521)
|
|
) mod FCapacity;
|
|
end;
|
|
|
|
procedure TDynHashArray.Remove(Item: Pointer);
|
|
begin
|
|
Delete(FindHashItem(Item));
|
|
end;
|
|
|
|
procedure TDynHashArray.Delete(ADynHashArrayItem: PDynHashArrayItem);
|
|
var Index: integer;
|
|
OldNext: PDynHashArrayItem;
|
|
begin
|
|
if ADynHashArrayItem=nil then exit;
|
|
// delete from cache
|
|
if (FHashCacheIndex>=0)
|
|
and ((ADynHashArrayItem^.Item=FHashCacheItem)
|
|
or (Assigned(OnGetKeyForHashItem)
|
|
and (OnGetKeyForHashItem(ADynHashArrayItem^.Item)=FHashCacheItem)))
|
|
then
|
|
// if the user removes an item, changes the key and readds it, the hash
|
|
// of the item can change
|
|
// => the cache must be cleared
|
|
ClearCache;
|
|
// delete from FItems
|
|
if not ADynHashArrayItem^.IsOverflow then begin
|
|
// Item is first item with hash
|
|
Index:=IndexOf(ADynHashArrayItem^.Item);
|
|
if Index < 0 then Exit; // should not happen
|
|
OldNext:=ADynHashArrayItem^.Next;
|
|
if (OldNext=nil) or (not (OldNext^.IsOverflow)) then
|
|
FItems[Index]:=nil
|
|
else begin
|
|
FItems[Index]:=OldNext;
|
|
OldNext^.IsOverflow:=false;
|
|
end;
|
|
end;
|
|
// adjust FFirstItem
|
|
if FFirstItem=ADynHashArrayItem then
|
|
FFirstItem:=FFirstItem^.Next;
|
|
// free storage item
|
|
DisposeHashItem(ADynHashArrayItem);
|
|
// adjust count and capacity
|
|
dec(FCount);
|
|
if FCount<FLowWaterMark then begin
|
|
// resize
|
|
SetCapacity((FCapacity+1) div 2);
|
|
end;
|
|
end;
|
|
|
|
procedure TDynHashArray.AssignTo(List: TList);
|
|
var
|
|
i: integer;
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
List.Count:=Count;
|
|
HashItem:=FirstHashItem;
|
|
i:=0;
|
|
while HashItem<>nil do begin
|
|
List[i]:=HashItem^.Item;
|
|
inc(i);
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynHashArray.AssignTo(List: TFPList);
|
|
var
|
|
i: integer;
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
List.Count:=Count;
|
|
HashItem:=FirstHashItem;
|
|
i:=0;
|
|
while HashItem<>nil do begin
|
|
List[i]:=HashItem^.Item;
|
|
inc(i);
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynHashArray.ForEach(const Func: TOnEachHashItem);
|
|
var
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
HashItem:=FFirstItem;
|
|
while HashItem<>nil do begin
|
|
if not Func(Self,HashItem^.Item) then break;
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
end;
|
|
|
|
function TDynHashArray.First: Pointer;
|
|
begin
|
|
if FFirstItem<>nil then
|
|
Result:=FFirstItem^.Item
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TDynHashArray.NewHashItem: PDynHashArrayItem;
|
|
begin
|
|
Result:=GetItemMemManager.NewItem;
|
|
end;
|
|
|
|
procedure TDynHashArray.DisposeHashItem(ADynHashArrayItem: PDynHashArrayItem);
|
|
begin
|
|
GetItemMemManager.DisposeItem(ADynHashArrayItem);
|
|
end;
|
|
|
|
function TDynHashArray.Contains(Item: Pointer): boolean;
|
|
begin
|
|
if (FContainsCache=nil) or (not TRecentList(FContainsCache).Contains(Item))
|
|
then begin
|
|
Result:=FindHashItem(Item)<>nil;
|
|
if Result and (FContainsCache<>nil) then
|
|
TRecentList(FContainsCache).Add(Item);
|
|
end else
|
|
Result:=true;
|
|
end;
|
|
|
|
function TDynHashArray.ContainsKey(Key: Pointer): boolean;
|
|
begin
|
|
Result:=FindHashItemWithKey(Key)<>nil;
|
|
end;
|
|
|
|
function TDynHashArray.FindHashItem(Item: Pointer): PDynHashArrayItem;
|
|
var Index: integer;
|
|
begin
|
|
if (Item<>nil) and (FItems<>nil) then begin
|
|
Index:=IndexOf(Item);
|
|
if Index>=0 then begin
|
|
Result:=FItems[Index];
|
|
if (Result<>nil) then begin
|
|
while (Result^.Item<>Item) do begin
|
|
Result:=Result^.Next;
|
|
if Result=nil then exit;
|
|
if Result^.IsOverflow=false then begin
|
|
Result:=nil;
|
|
exit;
|
|
end;
|
|
end;
|
|
SaveCacheItem(Item,Index);
|
|
end;
|
|
end else
|
|
Result:=nil;
|
|
end else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TDynHashArray.FindHashItemWithKey(Key: Pointer): PDynHashArrayItem;
|
|
var Index: integer;
|
|
begin
|
|
if FItems<>nil then begin
|
|
Index:=IndexOfKey(Key);
|
|
if Index>=0 then begin
|
|
Result:=FItems[Index];
|
|
if (Result<>nil) then begin
|
|
if Assigned(OnGetKeyForHashItem) then begin
|
|
if OnGetKeyForHashItem(Result^.Item)=Key then exit;
|
|
// search in overflow hash items
|
|
Result:=Result^.Next;
|
|
while (Result<>nil) and (Result^.IsOverflow) do begin
|
|
if OnGetKeyForHashItem(Result^.Item)=Key then begin
|
|
FHashCacheIndex:=Index;
|
|
FHashCacheItem:=Key;
|
|
exit;
|
|
end;
|
|
Result:=Result^.Next;
|
|
end;
|
|
Result:=nil;
|
|
end;
|
|
end;
|
|
end else
|
|
Result:=nil;
|
|
end else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TDynHashArray.FindItemWithKey(Key: Pointer): Pointer;
|
|
var
|
|
Index: integer;
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
Result:=nil;
|
|
if FItems<>nil then begin
|
|
Index:=IndexOfKey(Key);
|
|
if Index < 0 then Exit; // should not happen
|
|
HashItem:=FItems[Index];
|
|
if (HashItem<>nil)
|
|
and Assigned(OnGetKeyForHashItem) then begin
|
|
if OnGetKeyForHashItem(HashItem^.Item)=Key then exit;
|
|
// search in overflow hash items
|
|
HashItem:=HashItem^.Next;
|
|
while (HashItem<>nil) and (HashItem^.IsOverflow) do begin
|
|
if OnGetKeyForHashItem(HashItem^.Item)=Key then begin
|
|
FHashCacheIndex:=Index;
|
|
FHashCacheItem:=Key;
|
|
Result:=HashItem^.Item;
|
|
exit;
|
|
end;
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDynHashArray.GetHashItem(HashIndex: integer): PDynHashArrayItem;
|
|
begin
|
|
Result:=FItems[HashIndex];
|
|
end;
|
|
|
|
procedure TDynHashArray.SetCapacity(NewCapacity: integer);
|
|
var Size: integer;
|
|
begin
|
|
if NewCapacity<FMinCapacity then NewCapacity:=FMinCapacity;
|
|
if NewCapacity>FMaxCapacity then NewCapacity:=FMaxCapacity;
|
|
if NewCapacity=FCapacity then exit;
|
|
// resize FItems
|
|
FreeMem(FItems);
|
|
FCapacity:=NewCapacity;
|
|
Size:=FCapacity * SizeOf(PDynHashArrayItem);
|
|
GetMem(FItems,Size);
|
|
ComputeWaterMarks;
|
|
// rebuild
|
|
RebuildItems;
|
|
end;
|
|
|
|
procedure TDynHashArray.SetCustomHashFunction(const AValue: THashFunction);
|
|
begin
|
|
if FCustomHashFunction=AValue then exit;
|
|
FCustomHashFunction:=AValue;
|
|
FOwnerHashFunction:=nil;
|
|
RebuildItems;
|
|
end;
|
|
|
|
procedure TDynHashArray.SetOwnerHashFunction(const AValue: TOwnerHashFunction);
|
|
begin
|
|
if FOwnerHashFunction=AValue then exit;
|
|
FCustomHashFunction:=nil;
|
|
FOwnerHashFunction:=AValue;
|
|
RebuildItems;
|
|
end;
|
|
|
|
procedure TDynHashArray.RebuildItems;
|
|
var Index: integer;
|
|
CurHashItem, NextHashItem: PDynHashArrayItem;
|
|
begin
|
|
FillChar(FItems^,FCapacity * SizeOf(PDynHashArrayItem),0);
|
|
ClearCache;
|
|
CurHashItem:=FFirstItem;
|
|
FFirstItem:=nil;
|
|
while CurHashItem<>nil do begin
|
|
NextHashItem:=CurHashItem^.Next;
|
|
Index:=IndexOf(CurHashItem^.Item);
|
|
if Index < 0
|
|
then begin
|
|
// ??? something bad happenend
|
|
// should we dispose current item ?
|
|
// Anyhow, skip it.
|
|
CurHashItem := NextHashItem;
|
|
Continue;
|
|
end;
|
|
CurHashItem^.IsOverFlow:=false;
|
|
CurHashItem^.Prior:=nil;
|
|
if FItems[Index]=nil then begin
|
|
CurHashItem^.Next:=FFirstItem;
|
|
end else begin
|
|
CurHashItem^.Next:=FItems[Index];
|
|
CurHashItem^.Prior:=CurHashItem^.Next^.Prior;
|
|
CurHashItem^.Next^.IsOverflow:=true;
|
|
end;
|
|
if (CurHashItem^.Next=FFirstItem) then
|
|
FFirstItem:=CurHashItem;
|
|
FItems[Index]:=CurHashItem;
|
|
if CurHashItem^.Next<>nil then begin
|
|
CurHashItem^.Next^.Prior:=CurHashItem;
|
|
if CurHashItem^.Prior<>nil then
|
|
CurHashItem^.Prior^.Next:=CurHashItem;
|
|
end;
|
|
CurHashItem:=NextHashItem;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynHashArray.SaveCacheItem(Item: Pointer; Index: integer);
|
|
// Important:
|
|
// !!! Only call this method for items, that exists in the list or for items
|
|
// that can't change their key
|
|
begin
|
|
if Assigned(OnGetKeyForHashItem) then Item:=OnGetKeyForHashItem(Item);
|
|
FHashCacheItem:=Item;
|
|
FHashCacheIndex:=Index;
|
|
end;
|
|
|
|
constructor TDynHashArray.Create;
|
|
begin
|
|
Create(10);
|
|
end;
|
|
|
|
procedure TDynHashArray.SetOnGetKeyForHashItem(
|
|
const AValue: TOnGetKeyForHashItem);
|
|
begin
|
|
if FOnGetKeyForHashItem=AValue then exit;
|
|
FOnGetKeyForHashItem:=AValue;
|
|
RebuildItems;
|
|
end;
|
|
|
|
procedure TDynHashArray.SetOptions(const AValue: TDynHashArrayOptions);
|
|
begin
|
|
if FOptions=AValue then exit;
|
|
FOptions:=AValue;
|
|
if (FContainsCache<>nil) xor (dhaoCacheContains in Options) then begin
|
|
if FContainsCache=nil then begin
|
|
FContainsCache:=TRecentList.Create(5);
|
|
end else begin
|
|
FContainsCache.Free;
|
|
FContainsCache:=nil;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TDynHashArrayItemMemManager }
|
|
|
|
procedure TDynHashArrayItemMemManager.SetMaxFreeRatio(NewValue: integer);
|
|
begin
|
|
if NewValue<0 then NewValue:=0;
|
|
if NewValue=FMaxFreeRatio then exit;
|
|
FMaxFreeRatio:=NewValue;
|
|
end;
|
|
|
|
procedure TDynHashArrayItemMemManager.SetMinFree(NewValue: integer);
|
|
begin
|
|
if NewValue<0 then NewValue:=0;
|
|
if NewValue=FMinFree then exit;
|
|
FMinFree:=NewValue;
|
|
end;
|
|
|
|
procedure TDynHashArrayItemMemManager.DisposeFirstFreeItem;
|
|
var OldItem: PDynHashArrayItem;
|
|
begin
|
|
if FFirstFree=nil then exit;
|
|
OldItem:=FFirstFree;
|
|
FFirstFree:=OldItem^.Next;
|
|
if FFirstFree<>nil then
|
|
FFirstFree^.Prior:=nil;
|
|
Dispose(OldItem);
|
|
dec(FFreeCount);
|
|
end;
|
|
|
|
procedure TDynHashArrayItemMemManager.DisposeItem(
|
|
ADynHashArrayItem: PDynHashArrayItem);
|
|
begin
|
|
if ADynHashArrayItem=nil then exit;
|
|
// unbind item
|
|
if ADynHashArrayItem^.Next<>nil then
|
|
ADynHashArrayItem^.Next^.Prior:=ADynHashArrayItem^.Prior;
|
|
if ADynHashArrayItem^.Prior<>nil then
|
|
ADynHashArrayItem^.Prior^.Next:=ADynHashArrayItem^.Next;
|
|
// add to free list
|
|
ADynHashArrayItem^.Next:=FFirstFree;
|
|
FFirstFree:=ADynHashArrayItem;
|
|
if ADynHashArrayItem^.Next<>nil then
|
|
ADynHashArrayItem^.Next^.Prior:=ADynHashArrayItem;
|
|
ADynHashArrayItem^.Prior:=nil;
|
|
inc(FFreeCount);
|
|
// reduce free list
|
|
if (FFreeCount>(((8+FMaxFreeRatio)*FCount) shr 3)) and (FFreeCount>10) then
|
|
begin
|
|
DisposeFirstFreeItem;
|
|
DisposeFirstFreeItem;
|
|
end;
|
|
end;
|
|
|
|
function TDynHashArrayItemMemManager.NewItem: PDynHashArrayItem;
|
|
begin
|
|
if FFirstFree<>nil then begin
|
|
Result:=FFirstFree;
|
|
FFirstFree:=FFirstFree^.Next;
|
|
if FFirstFree<>nil then
|
|
FFirstFree^.Prior:=nil;
|
|
dec(FFreeCount);
|
|
end else begin
|
|
New(Result);
|
|
end;
|
|
with Result^ do begin
|
|
Item:=nil;
|
|
Next:=nil;
|
|
Prior:=nil;
|
|
IsOverflow:=false;
|
|
end;
|
|
end;
|
|
|
|
procedure TDynHashArrayItemMemManager.Clear;
|
|
begin
|
|
while FFreeCount>0 do DisposeFirstFreeItem;
|
|
end;
|
|
|
|
constructor TDynHashArrayItemMemManager.Create;
|
|
begin
|
|
inherited Create;
|
|
FFirstFree:=nil;
|
|
FFreeCount:=0;
|
|
FCount:=0;
|
|
FMinFree:=100;
|
|
FMaxFreeRatio:=8; // 1:1
|
|
end;
|
|
|
|
destructor TDynHashArrayItemMemManager.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TDynHashArrayItemMemManager.ConsistencyCheck: integer;
|
|
var RealFreeCount: integer;
|
|
HashItem: PDynHashArrayItem;
|
|
begin
|
|
RealFreeCount:=0;
|
|
HashItem:=FFirstFree;
|
|
while HashItem<>nil do begin
|
|
inc(RealFreeCount);
|
|
if (HashItem^.Next<>nil) and (HashItem^.Next^.Prior<>HashItem) then
|
|
exit(-1);
|
|
if (HashItem^.Prior<>nil) and (HashItem^.Prior^.Next<>HashItem) then
|
|
exit(-2);
|
|
HashItem:=HashItem^.Next;
|
|
end;
|
|
if RealFreeCount<>FFreeCount then exit(-3);
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TDynHashArrayItemMemManager.WriteDebugReport;
|
|
begin
|
|
DebugLn('TDynHashArrayItemMemManager.WriteDebugReport:'
|
|
,' Consistency=',dbgs(ConsistencyCheck),', FreeCount=',dbgs(FFreeCount));
|
|
end;
|
|
|
|
//==============================================================================
|
|
|
|
finalization
|
|
FreeAndNil(ItemMemManager);
|
|
|
|
end.
|