{*********************************************************} {* OVCSPARY.PAS 4.06 *} {*********************************************************} {* ***** BEGIN LICENSE BLOCK ***** *} {* Version: MPL 1.1 *} {* *} {* The contents of this file are subject to the Mozilla Public License *} {* Version 1.1 (the "License"); you may not use this file except in *} {* compliance with the License. You may obtain a copy of the License at *} {* http://www.mozilla.org/MPL/ *} {* *} {* Software distributed under the License is distributed on an "AS IS" basis, *} {* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License *} {* for the specific language governing rights and limitations under the *} {* License. *} {* *} {* The Original Code is TurboPower Orpheus *} {* *} {* The Initial Developer of the Original Code is TurboPower Software *} {* *} {* Portions created by TurboPower Software Inc. are Copyright (C)1995-2002 *} {* TurboPower Software Inc. All Rights Reserved. *} {* *} {* Contributor(s): *} {* *} {* ***** END LICENSE BLOCK ***** *} {$I OVC.INC} {$B-} {Complete Boolean Evaluation} {$I+} {Input/Output-Checking} {$P+} {Open Parameters} {$T-} {Typed @ Operator} {.W-} {Windows Stack Frame} {$X+} {Extended Syntax} unit ovcspary; {-Orpheus - sparse array implementation} interface uses {$IFNDEF LCL} Windows, {$ELSE} LclIntf, {$ENDIF} SysUtils, OvcExcpt, OvcConst, OvcData; const MaxSparseArrayItems = 320000; {maximum items in a sparse array} type TSparseArrayFunc = function (Index : longint; Item : pointer; ExtraData : pointer) : boolean; {-Sparse array's iterator type. Should return true to continue iterating, false otherwise.} {The sparse array class} TOvcSparseArray = class protected {private} FCount : longint; {Fake count of the items} FArray : pointer; {Sparse array} ChunkCount : word; {Number of chunks} ChunkArraySize : word; {Size of FArray} procedure RecalcCount; protected function GetActiveCount : longint; function GetItem(Index : longint) : pointer; procedure PutItem(Index : longint; Item : pointer); public constructor Create; destructor Destroy; override; function Add(Item : pointer) : longint; {-Add Item to end of array} procedure Clear; {-Clear array} procedure Delete(Index : longint); {-Delete item at Index, all items below move up one} procedure Exchange(Index1, Index2 : longint); {-Swap the items at Index1 and Index2} function First : pointer; {-Return First item} function ForAll(Action : TSparseArrayFunc; Backwards : boolean; ExtraData : pointer) : longint; {-Iterate through all active items, maybe backwards} function IndexOf(Item : pointer) : longint; {-Get the index of Item} procedure Insert(Index : longint; Item : pointer); {-Insert Item at Index, it and all items below move down one} function Last : pointer; {-Return Last item} procedure Squeeze; {-Pack the sparse array} property Count : longint {-Logical count of the number of items (=IndexOf(Last)+1)} read FCount; property ActiveCount : longint {-Count of non-nil items} read GetActiveCount; property Items[Index : longint] : pointer {-Items array} read GetItem write PutItem; default; end; implementation {Notes: the sparse array is implemented as an array of chunks, each chunk contains 32 items (2^5). The array of chunks consists of a set of elements, each with chunk index and a pointer to the chunk. To find the item for a given index you do two things: calculate the chunk index (divide by 32) and the index into the chunk (the remainder once divided by 32). For example: where is item 100? 100 = 3*32 + 4, so you try and find chunk index 3 in the chunk array (the array is sorted by chunk index, hence you do a binary search), and if found return the 5th element (zero based arrays). If not found then the item does not exist. Thus setting item 10000 in a sparse array will allocate only one chunk, the 9999 previous items are all assumed nil. The sparse array can only accomodate pointers. An unused item will be nil. A nil pointer will indicate an unused item. Hence you cannot really use a sparse array for longints say, unless you can guarantee that all your values will be non-nil. Sizing stuff: maximum number of pointers that can be stored is just less than 350,000. For various reasons the maximum index that is allowed for the sparse array is 319,999 meaning that a sparse array could hold up to 320,000 pointers. To increase this you could hold 64 or 128 pointers per chunk instead. The minimum heap the sparse array will take is 896 bytes. Sparse arrays cannot really be used for keeping sorted items: obviously all the items will appear at the start of the array, there can be no holes. } const ShiftValue = 5; ChunkElements = 1 shl ShiftValue; {Number of elements in a chunk: 32} ChunkMask = pred(ChunkElements); {Mask used for the item in a chunk: $1F} type PChunk = ^TChunk; {Definition of a chunk} TChunk = array [0..pred(ChunkElements)] of pointer; TChunkArrayElement = packed record {Definition of a chunk array element} ChunkIndex : word; {..index of the chunk} Chunk : PChunk; {..the chunk itself} end; const DefChunkArrayElements = 4; {Initial size of the chunk array} MaxChunkArrayElements = ($10000 div sizeof(TChunkArrayElement)) - 1; {Absolute maximum of chunk array elements} type PChunkArray = ^TChunkArray; {Definition of a chunk array} TChunkArray = array [0..pred(MaxChunkArrayElements)] of TChunkArrayElement; {===Helper routines==================================================} procedure RaiseException(ClassType : integer); begin case ClassType of 1 : raise ESAEAtMaxSize.Create(GetOrphStr(SCSAEAtMaxSize)); 2 : raise ESAEOutOfBounds.Create(GetOrphStr(SCSAEOutOfBounds)); else raise ESparseArrayError.Create(GetOrphStr(SCSAEGeneral)); end;{case} end; {--------} function GrowChunkArray(A : PChunkArray; var CurSize : word) : PChunkArray; {-Grow the chunk array, return the new size and the new pointer} var NewSize : longint; NewSizeAdj : word; begin NewSize := longint(CurSize) + (DefChunkArrayElements * sizeof(TChunkArrayElement)); NewSizeAdj := MaxChunkArrayElements * sizeof(TChunkArrayElement); if (NewSize < NewSizeAdj) then NewSizeAdj := NewSize; GetMem(Result, NewSizeAdj); {$IFOPT D+} FillChar(Result^, NewSizeAdj, $CC); {$ENDIF} if (CurSize <> 0) then begin Move(A^, Result^, CurSize); FreeMem(A, CurSize); end; CurSize := NewSizeAdj; end; {--------} function GetChunk(A : PChunkArray; CI : word; NumChunks : word) : integer; {-Find a chunk array element given the chunk index CI and the number of chunks. Return the index into the chunk array, or -1 if not found.} var L, R : integer; MsInx : word; begin L := 0; R := pred(NumChunks); repeat Result := (L + R) div 2; MsInx := A^[Result].ChunkIndex; if (CI = MsInx) then Exit else if (CI < MsInx) then R := pred(Result) else L := succ(Result); until (L > R); Result := -1; end; {--------} function EnsureChunk(var A : PChunkArray; CI : word; var NumChunks, Size : word; DontCreate : boolean) : integer; {-Makes sure that chunk CI is available for use. If it does not yet exist and DontCreate is false, creates a new chunk, inserts it into the chunk array (possibly growing the array). Return the index of the chunk in the array.} var NumElements : word; L, R, M : integer; MsInx : word; begin L := 0; if (NumChunks > 0) then begin R := pred(NumChunks); repeat M := (L + R) div 2; MsInx := A^[M].ChunkIndex; if (CI = MsInx) then begin Result := M; Exit; end else if (CI < MsInx) then R := pred(M) else L := succ(M); until (L > R); end; if DontCreate then begin Result := -1; Exit; end; Result := L; NumElements := Size div sizeof(TChunkArrayElement); if (NumChunks = NumElements) then A := GrowChunkArray(A, Size); if (Result < NumChunks) then Move(A^[Result], A^[succ(Result)], (NumChunks - Result) * sizeof(TChunkArrayElement)); with A^[Result] do begin ChunkIndex := CI; Chunk := New(PChunk); FillChar(Chunk^, sizeof(TChunk), 0); end; inc(NumChunks); end; {--------} function ChunkIsBlank(A : PChunkArray; ArrayInx : word) : boolean; {-Return true if the chunk has no items (all pointers are nil).} const ChunkSizeInWords = sizeof(TChunk) div 2; ChunkSizeInDWords = sizeof(TChunk) div 4; var Chunk : PChunk; {$IFDEF NoAsm} ItemNum : Integer; {$ENDIF} begin Chunk := A^[ArrayInx].Chunk; {$IFDEF NoAsm} for ItemNum := 0 to Pred(ChunkElements) do begin if Chunk^[ItemNum] <> nil then begin Result := False; Exit; end; end; Result := True; {$ELSE} asm push edi {$IFDEF VERSION6} { Delphi 6 codegen bug } push ecx {$ENDIF} lea eax, Chunk mov edi, [eax] xor eax, eax mov edx, eax mov ecx, ChunkSizeInDWords repe scasd jne @@Exit inc edx @@Exit: {$IFDEF VERSION6} { Delphi 6 codegen bug } pop ecx {$ENDIF} mov @Result, dl pop edi end; {$ENDIF} end; {--------} procedure DeleteChunk(A : PChunkArray; ArrayInx : word; var NumChunks : word); {-Delete a chunk, moving chunks below up one.} begin Dispose(A^[ArrayInx].Chunk); if ArrayInx < pred(NumChunks) then Move(A^[succ(ArrayInx)], A^[ArrayInx], (NumChunks - ArrayInx) * sizeof(TChunkArrayElement)); dec(NumChunks); {$IFOPT D+} FillChar(A^[NumChunks], sizeof(TChunkArrayElement), $CC); {$ENDIF} end; {===TOvcSparseArray ForAll routines=====================================} function CountActiveElements(Index : longint; Item : pointer; ExtraData : pointer) : boolean; far; var ED : ^longint absolute ExtraData; begin Result := True; inc(ED^); end; {=====} function Find1stOrLastElement(Index : longint; Item : pointer; ExtraData : pointer) : boolean; far; var ED : ^pointer absolute ExtraData; begin Find1stOrLastElement := false; ED^ := Item; end; {=====} function FindSpecificElement(Index : longint; Item : pointer; ExtraData : pointer) : boolean; far; begin {continue looking if this Item is NOT the one we want} FindSpecificElement := Item <> ExtraData; end; {=====} constructor TOvcSparseArray.Create; begin FArray := GrowChunkArray(FArray, ChunkArraySize); end; {=====} destructor TOvcSparseArray.Destroy; begin if Assigned(FArray) then begin Clear; FreeMem(FArray, ChunkArraySize); end; end; {=====} procedure TOvcSparseArray.RecalcCount; var Dummy : pointer; begin FCount := succ(ForAll(Find1stOrLastElement, true, @Dummy)); end; {--------} procedure TOvcSparseArray.Squeeze; var ArrayInx : word; begin ArrayInx := 0; while ArrayInx <> ChunkCount do if ChunkIsBlank(FArray, ArrayInx) then DeleteChunk(FArray, ArrayInx, ChunkCount) else inc(ArrayInx); end; {=======================================================================} {===TOvcSparseArray property access=====================================} function TOvcSparseArray.GetActiveCount : longint; begin Result := 0; ForAll(CountActiveElements, true, @Result); end; {--------} function TOvcSparseArray.GetItem(Index : longint) : pointer; var ChunkIndex : word; ChunkNum : integer; begin if (Index < 0) or (Index >= MaxSparseArrayItems) then begin RaiseException(2); end; Result := nil; if (ChunkCount > 0) then begin ChunkIndex := Index shr ShiftValue; ChunkNum := GetChunk(FArray, ChunkIndex, ChunkCount); if (ChunkNum <> -1) then Result := PChunkArray(FArray)^[ChunkNum].Chunk^[Index and ChunkMask]; end; end; {--------} procedure TOvcSparseArray.PutItem(Index : longint; Item : pointer); var ChunkIndex : word; ChunkNum : integer; begin if (Index < 0) or (Index >= MaxSparseArrayItems) then begin RaiseException(2); end; ChunkIndex := Index shr ShiftValue; ChunkNum := EnsureChunk(PChunkArray(FArray), ChunkIndex, ChunkCount, ChunkArraySize, (Item = nil)); if (ChunkNum <> -1) then begin PChunkArray(FArray)^[ChunkNum].Chunk^[Index and ChunkMask] := Item; if (Item = nil) then Squeeze; RecalcCount; end; end; {====================================================================} {===TOvcSparseArray item maintenance====================================} function TOvcSparseArray.Add(Item : pointer) : longint; begin if (FCount = MaxSparseArrayItems) then RaiseException(1); Result := FCount; PutItem(Result, Item); end; {--------} procedure TOvcSparseArray.Clear; var i : integer; begin if (ChunkCount > 0) then begin for i := 0 to pred(ChunkCount) do Dispose(PChunkArray(FArray)^[i].Chunk); {$IFOPT D+} FillChar(FArray^, ChunkArraySize, $CC); {$ENDIF} end; ChunkCount := 0; FCount := 0; end; {--------} procedure TOvcSparseArray.Delete(Index : longint); const LastPos = pred(ChunkElements); var MajorInx : word; ChunkNum, Dummy : integer; StartPos : word; OurChunk : PChunk; Transferred : boolean; begin if (Index < 0) or (Index >= MaxSparseArrayItems) then begin RaiseException(2); end; if (Index >= FCount) then Exit; MajorInx := Index shr ShiftValue; ChunkNum := EnsureChunk(PChunkArray(FArray), MajorInx, ChunkCount, ChunkArraySize, false); StartPos := Index and ChunkMask; OurChunk := PChunkArray(FArray)^[ChunkNum].Chunk; if (StartPos <> LastPos) then Move(OurChunk^[succ(StartPos)], OurChunk^[StartPos], (LastPos-StartPos)*sizeof(Pointer)); inc(ChunkNum); while (ChunkNum <> ChunkCount) do begin with PChunkArray(FArray)^[ChunkNum] do begin if (ChunkIndex = MajorInx+1) then begin Transferred := true; OurChunk^[LastPos] := Chunk^[0]; end else begin Transferred := false; OurChunk^[LastPos] := nil; end; MajorInx := ChunkIndex; OurChunk := Chunk; end; if (OurChunk^[0] <> nil) and (not Transferred) then begin Dummy := EnsureChunk(PChunkArray(FArray), MajorInx-1, ChunkCount, ChunkArraySize, true); PChunkArray(FArray)^[Dummy].Chunk^[LastPos] := OurChunk^[0]; end; Move(OurChunk^[1], OurChunk^[0], LastPos*sizeof(Pointer)); inc(ChunkNum); end; OurChunk^[LastPos] := nil; Squeeze; RecalcCount; end; {--------} procedure TOvcSparseArray.Exchange(Index1, Index2 : longint); var Item1, Item2 : pointer; begin if (Index1 = Index2) then Exit; if (Index1 < 0) or (Index1 >= MaxSparseArrayItems) then begin RaiseException(2); end; if (Index2 < 0) or (Index2 >= MaxSparseArrayItems) then begin RaiseException(2); end; Item1 := GetItem(Index1); Item2 := GetItem(Index2); PutItem(Index2, Item1); PutItem(Index1, Item2); end; {--------} function TOvcSparseArray.First : pointer; begin Result := nil; ForAll(Find1stOrLastElement, false, @Result); end; {--------} function TOvcSparseArray.ForAll(Action : TSparseArrayFunc; Backwards : boolean; ExtraData : pointer) : longint; var MajorInx : word; MinorInx : word; MajorStub : longint; label ExitLoopsReverse, ExitLoopsForwards; begin if (ChunkCount = 0) then Result := -1 else if Backwards then begin for MajorInx := pred(ChunkCount) downto 0 do with PChunkArray(FArray)^[MajorInx] do begin MajorStub := longint(ChunkIndex) shl ShiftValue; for MinorInx := pred(ChunkElements) downto 0 do if (Chunk^[MinorInx] <> nil) then begin Result := MajorStub + MinorInx; if not Action(Result, Chunk^[MinorInx], ExtraData) then Goto ExitLoopsReverse; end; end; Result := -1; ExitLoopsReverse: end else begin for MajorInx := 0 to pred(ChunkCount) do with PChunkArray(FArray)^[MajorInx] do begin MajorStub := longint(ChunkIndex) shl ShiftValue; for MinorInx := 0 to pred(ChunkElements) do if (Chunk^[MinorInx] <> nil) then begin Result := MajorStub + MinorInx; if not Action(Result, Chunk^[MinorInx], ExtraData) then Goto ExitLoopsForwards; end; end; Result := -1; ExitLoopsForwards: end; end; {--------} function TOvcSparseArray.IndexOf(Item : pointer) : longint; begin Result := ForAll(FindSpecificElement, true, Item); end; {--------} procedure TOvcSparseArray.Insert(Index : longint; Item : pointer); const LastPos = pred(ChunkElements); var MajorInx : word; ChunkNum : integer; CarryItem, NewCarryItem : pointer; StartPos : word; begin if (Index < 0) or (Index >= MaxSparseArrayItems) then begin RaiseException(2); end; if (FCount = MaxSparseArrayItems) then RaiseException(1); if (Index >= FCount) then begin PutItem(Index, Item); Exit; end; MajorInx := Index shr ShiftValue; ChunkNum := EnsureChunk(PChunkArray(FArray), MajorInx, ChunkCount, ChunkArraySize, false); CarryItem := Item; StartPos := Index and ChunkMask; repeat with PChunkArray(FArray)^[ChunkNum] do begin MajorInx := ChunkIndex; NewCarryItem := Chunk^[LastPos]; if (StartPos <> LastPos) then Move(Chunk^[StartPos], Chunk^[succ(StartPos)], (LastPos-StartPos)*sizeof(Pointer)); Chunk^[StartPos] := CarryItem; CarryItem := NewCarryItem; StartPos := 0; end; inc(ChunkNum); if (CarryItem <> nil) then if (ChunkNum = ChunkCount) or (PChunkArray(FArray)^[ChunkNum].ChunkIndex <> MajorInx+1) then ChunkNum := EnsureChunk(PChunkArray(FArray), MajorInx+1, ChunkCount, ChunkArraySize, false); until (ChunkNum = ChunkCount); inc(FCount); end; {--------} function TOvcSparseArray.Last : pointer; begin Result := nil; ForAll(Find1stOrLastElement, true, @Result); end; {====================================================================} end.