613 lines
15 KiB
ObjectPascal
613 lines
15 KiB
ObjectPascal
{ *************************************************************************** }
|
|
{ }
|
|
{ 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.
|