{ *************************************************************************** } { } { EControl Common Library } { } { Copyright (c) 2004 - 2015 EControl Ltd., Zaharov Michael } { www.econtrol.ru } { support@econtrol.ru } { } { *************************************************************************** } {$mode delphi} {$define EC_UNICODE} unit ecLists; interface uses Classes; type TSortedItem = class protected function GetKey: integer; virtual; abstract; end; TSortedList = class private FList: TList; function GetCount: integer; function GetItem(Index: integer): TSortedItem; public constructor Create(OwnObjects: Boolean); destructor Destroy; override; function Add(Item: TSortedItem): integer; procedure Delete(Index: integer); procedure Remove(Item: TSortedItem); procedure Clear; function PriorAt(Pos: integer): integer; function GetAt(Pos: integer): TSortedItem; function GetIndexAt(Pos: integer): integer; property Items[Index: integer]: TSortedItem read GetItem; default; property Count: integer read GetCount; end; TRange = class(TSortedItem) private function GetLength: integer; {$IFDEF EC_DOTNET} public {$ELSE} protected {$ENDIF} FStartPos: integer; FEndPos: integer; protected function GetKey: integer; override; public constructor Create(AStartPos, AEndPos: integer); property StartPos: integer read FStartPos; property EndPos: integer read FEndPos; property Size: integer read GetLength; end; // Array of sorted ranges TRangeList = class private FList: TList; FUnionSiblings: Boolean; FPrevIdx: integer; function GetCount: integer; function GetItems(Index: integer): TRange; protected // Union ranges with the [Index] and [Index + 1] // returns new range index (or union result) function UnionRanges(Index: integer): integer; virtual; function IsGreater(I1, I2: integer): Boolean; // Try to add range if there are no intersections function TryAdd(Range: TRange): Boolean; public constructor Create(UnionSiblings: Boolean = True); destructor Destroy; override; function Add(Range: TRange): integer; virtual; procedure Delete(Index: integer); procedure Clear; virtual; function ClearFromPos(APos: integer; CopyTo: TRangeList = nil): integer; virtual; // Deletes ranges that intersect the bounds, returns number of deleted items function DeleteIntersected(AStart, AEnd: integer): integer; function SplitRange(RangeIdx, SplitPos: integer): Boolean; // Content has been changed, updates ranges upper Pos // Removes affected ranges function ContentChanged(Pos, Count: integer): Boolean; // Exactly at position function RangeAt(APos: integer): integer; // At position or next function NextAt(APos: integer): integer; // At position or prior function PriorAt(APos: integer): integer; property Count: integer read GetCount; property Items[Index: integer]: TRange read GetItems; default; end; // collection of overlapped ranges // all ranges are sorted for quick search TRangeCollection = class private FRangeArrays: TList; function GetLevelCount: integer; function GetLevels(Index: integer): TRangeList; public constructor Create; destructor Destroy; override; procedure Add(Range: TRange); procedure Clear; procedure ClearFromPos(APos: integer); function GetRangesAtPos(List: TList; Pos: integer): integer; function GetRangesAtRange(List: TList; StartPos, EndPos: integer): integer; property LevelCount: integer read GetLevelCount; property Levels[Index: integer]: TRangeList read GetLevels; default; end; // List[Index] > Key => Result > 0 // List[Index] = Key => Result = 0 // List[Index] < Key => Result < 0 TCompareProc = function(const List: TList; Index: integer; Key: TObject): integer; // Search in sorted list function QuickSearch(const List: TList; CompProc: TCompareProc; Key: TObject; var Index: integer): Boolean; implementation uses SysUtils, Contnrs; function QuickSearch(const List: TList; CompProc: TCompareProc; Key: TObject; var Index: integer): Boolean; var L, H, I, C: Integer; begin Result := False; if List.Count = 0 then begin Index := -1; Exit; end; L := 0; H := List.Count - 1; while L <= H do begin I := (L + H) shr 1; C := CompProc(List, I, Key); if C < 0 then L := I + 1 else begin if C = 0 then begin Result := True; Index := I; Exit; end; H := I - 1; end; end; Index := L; if Index >= List.Count then Index := List.Count - 1; if Index >= 0 then if CompProc(List, Index, Key) > 0 then dec(Index); end; { TRange } constructor TRange.Create(AStartPos, AEndPos: integer); begin inherited Create; FStartPos := AStartPos; FEndPos := AEndPos; end; function TRange.GetKey: integer; begin Result := FStartPos; end; function TRange.GetLength: integer; begin Result := FEndPos - FStartPos; end; { TRangeList } constructor TRangeList.Create(UnionSiblings: Boolean); begin inherited Create; FList := TObjectList.Create; FUnionSiblings := UnionSiblings; end; destructor TRangeList.Destroy; begin FreeAndNil(FList); //AT inherited; end; procedure TRangeList.Clear; begin FList.Clear; end; function TRangeList.UnionRanges(Index: integer): integer; begin // Default action - union of ranges if Items[Index].FEndPos < Items[Index + 1].FEndPos then Items[Index].FEndPos := Items[Index + 1].FEndPos; FList.Delete(Index + 1); Result := Index; end; function TRangeList.IsGreater(I1, I2: integer): Boolean; begin if FUnionSiblings then Result := I1 >= I2 else Result := I1 > I2; end; function TRangeList.TryAdd(Range: TRange): Boolean; var idx: integer; begin Result := True; if (Count = 0) or (Items[Count - 1].EndPos <= Range.StartPos) then FList.Add(Range) else if Items[Count - 1].StartPos <= Range.StartPos then Result := False else begin idx := NextAt(Range.StartPos); if idx = -1 then FList.Add(Range) else if (Items[idx].StartPos >= Range.EndPos) and ((idx = 0) or (Items[idx - 1].EndPos <= Range.StartPos)) then FList.Insert(idx, Range) else Result := False; end; end; function TRangeList.Add(Range: TRange): integer; var idx, k: integer; begin // Stream adding if (Count = 0) or (Items[Count - 1].EndPos < Range.StartPos) then begin Result := Count; FList.Add(Range); Exit; end; idx := PriorAt(Range.StartPos); if idx = Count - 1 then FList.Add(Range) else FList.Insert(idx + 1, Range); // Lower range check if (idx <> -1) and IsGreater(Items[idx].EndPos, Range.StartPos) then idx := UnionRanges(idx) else idx := idx + 1; k := idx + 1; while (k < Count) and IsGreater(Items[idx].EndPos, Items[k].StartPos) do begin idx := UnionRanges(idx); k := idx + 1; end; Result := idx; end; procedure TRangeList.Delete(Index: integer); begin FList.Delete(Index); end; function TRangeList.GetCount: integer; begin Result := FList.Count; end; function TRangeList.GetItems(Index: integer): TRange; begin Result := TRange(FList[Index]); end; function TRangeList.RangeAt(APos: integer): integer; begin Result := PriorAt(APos); if (Result <> -1) and (Items[Result].EndPos <= APos) then Result := -1; end; function TRangeList.NextAt(APos: integer): integer; begin Result := PriorAt(APos); if Result = -1 then begin if Count > 0 then Result := 0 end else if Items[Result].EndPos <= APos then if Result < Count - 1 then Inc(Result) else Result := -1; end; function RangeCmp(const List: TList; Index: integer; Key: TObject): integer; begin with TRange(List[Index]) do if FStartPos > integer(Key) then Result := 1 else if (FStartPos <= integer(Key)) and (FEndPos > integer(Key)) then Result:= 0 else Result := -1; end; function TRangeList.PriorAt(APos: integer): integer; begin if (FPrevIdx >= 0) and (FPrevIdx < FList.Count - 1) then begin if TRange(FList[FPrevIdx]).StartPos <= APos then if (FPrevIdx >= FList.Count - 1) or (TRange(FList[FPrevIdx + 1]).StartPos > APos) then begin Result := FPrevIdx; Exit; end else if (FPrevIdx >= FList.Count - 2) or (TRange(FList[FPrevIdx + 2]).StartPos > APos) then begin Result := FPrevIdx + 1; Exit; end; end; QuickSearch(FList, RangeCmp, TObject(APos), Result); FPrevIdx := Result; end; function TRangeList.ContentChanged(Pos, Count: integer): Boolean; var idx: integer; begin idx := PriorAt(Pos); if (idx <> -1) and (Items[idx].EndPos >= Pos) then Delete(idx) else begin Inc(idx); if idx >= FList.Count then // No change begin Result := False; Exit; end; end; if Count < 0 then while (idx < FList.Count) and (Items[idx].StartPos <= Pos - Count) do Delete(idx); while idx < FList.Count do begin Inc(Items[idx].FStartPos, Count); Inc(Items[idx].FEndPos, Count); Inc(idx); end; Result := True; end; function TRangeList.ClearFromPos(APos: integer; CopyTo: TRangeList): integer; var idx, i: integer; begin Result := APos; idx := NextAt(APos); if idx <> -1 then begin if Items[idx].StartPos < APos then Result := Items[idx].StartPos; if CopyTo <> nil then begin CopyTo.Clear; CopyTo.FList.Capacity := Count - idx; for i := idx to Count - 1 do begin CopyTo.FList.Add(FList[i]); FList.List[i] := nil; end; // N := Count - idx; // CopyTo.FList.Count := N; // Move(FList.List[idx], CopyTo.FList.List[0], N * sizeof(pointer)); // for i := Count - 1 downto idx do // FList.List[idx] := nil; end; for i := Count - 1 downto idx do Delete(i); end; end; function TRangeList.DeleteIntersected(AStart, AEnd: integer): integer; var idx: integer; begin idx := NextAt(AStart); if idx = -1 then idx := Count - 1 else if Items[idx].StartPos >= AEnd then Dec(idx); Result := 0; while (idx >= 0) and (idx < Count) and (Items[idx].EndPos > Astart) do begin Inc(Result); Delete(idx); end; end; type TRangeClass = class of TRange; function TRangeList.SplitRange(RangeIdx, SplitPos: integer): Boolean; var R: TRange; sp: integer; begin R := Items[RangeIdx]; Result := (SplitPos > R.StartPos) and (SplitPos < R.EndPos); if Result then begin sp := R.StartPos; R.FStartPos := SplitPos; R := TRangeClass(R.ClassType).Create(sp, SplitPos); FList.Insert(RangeIdx, R); end; end; { TRangeCollection } constructor TRangeCollection.Create; begin inherited; FRangeArrays := TObjectList.Create; end; destructor TRangeCollection.Destroy; begin Clear; FreeAndNil(FRangeArrays); end; procedure TRangeCollection.Clear; begin FRangeArrays.Clear; end; procedure TRangeCollection.Add(Range: TRange); var i: integer; R: TRangeList; begin for i := 0 to FRangeArrays.Count - 1 do if TRangeList(FRangeArrays[i]).TryAdd(Range) then Exit; R := TRangeList.Create(False); FRangeArrays.Add(R); R.Add(Range); end; procedure TRangeCollection.ClearFromPos(APos: integer); var i: integer; R: TRangeList; begin for i := FRangeArrays.Count - 1 downto 0 do begin R := TRangeList(FRangeArrays[i]); R.ClearFromPos(APos); if R.Count = 0 then FRangeArrays.Delete(i); end; end; function TRangeCollection.GetRangesAtPos(List: TList; Pos: integer): integer; var i, idx: integer; R: TRangeList; begin Result := -1; for i := 0 to FRangeArrays.Count - 1 do begin R := TRangeList(FRangeArrays[i]); idx := R.NextAt(Pos); if idx <> -1 then begin if R[idx].StartPos <= Pos then begin List.Add(R[idx]); if (Result = -1) or (Result > R[idx].EndPos) then Result := R[idx].EndPos; end else if (Result = -1) or (Result > R[idx].StartPos) then Result := R[idx].StartPos; end; end; end; function TRangeCollection.GetRangesAtRange(List: TList; StartPos, EndPos: integer): integer; var i, idx: integer; R: TRangeList; begin Result := -1; for i := 0 to FRangeArrays.Count - 1 do begin R := TRangeList(FRangeArrays[i]); idx := R.NextAt(StartPos); if (idx <> -1) then while (Idx < R.Count) and (R[idx].StartPos < EndPos) do begin List.Add(R[idx]); Inc(Idx); end; end; end; function TRangeCollection.GetLevelCount: integer; begin Result := FRangeArrays.Count; end; function TRangeCollection.GetLevels(Index: integer): TRangeList; begin Result := TRangeList(FRangeArrays[Index]); end; { TSortedList } function TSortedList.Add(Item: TSortedItem): integer; begin if (Count = 0) or (Items[Count - 1].GetKey <= Item.GetKey) then begin Result := Count; FList.Add(Item); end else begin Result := PriorAt(Item.GetKey); Inc(Result); if Result = Count then FList.Add(Item) else FList.Insert(Result, Item); end; end; procedure TSortedList.Clear; begin FList.Clear; end; constructor TSortedList.Create(OwnObjects: Boolean); begin inherited Create; if OwnObjects then FList := TObjectList.Create else FList := TList.Create; end; procedure TSortedList.Delete(Index: integer); begin FList.Delete(Index); end; destructor TSortedList.Destroy; begin FreeAndNil(FList);//AT inherited; end; function TSortedList.GetAt(Pos: integer): TSortedItem; var idx: integer; begin idx := GetIndexAt(Pos); if idx = -1 then Result := nil else Result := Items[idx]; end; function TSortedList.GetIndexAt(Pos: integer): integer; begin Result := PriorAt(Pos); if (Result <> -1) and (Items[Result].GetKey <> Pos) then Result := -1; end; function TSortedList.GetCount: integer; begin if FList<>nil then//AT Result := FList.Count else Result:= 0;//AT end; function TSortedList.GetItem(Index: integer): TSortedItem; begin Result := TSortedItem(FList[Index]); end; function ItemCmp(const List: TList; Index: integer; Key: TObject): integer; begin Result := TSortedItem(List[Index]).GetKey - integer(Key); end; function TSortedList.PriorAt(Pos: integer): integer; begin QuickSearch(FList, ItemCmp, TObject(Pos), Result); end; procedure TSortedList.Remove(Item: TSortedItem); begin FList.Remove(Item); end; end.