649 lines
15 KiB
ObjectPascal
649 lines
15 KiB
ObjectPascal
{
|
|
:: X2UtBinaryTree contains an implementation of the binary tree algorithm,
|
|
:: along with various descendants which implement support for a range of value
|
|
:: types (such as pointers, integers and strings). This effectively makes it
|
|
:: an associative array based on an integer key. For a hash implementation
|
|
:: based on string keys use the X2UtHashes unit.
|
|
::
|
|
:: P.S. I realise that a "B-Tree" is different from a binary tree. For
|
|
:: convenience reasons I will however ignore your ranting and call my
|
|
:: classes "TX2UtBTree". ;)
|
|
::
|
|
:: Last changed: $Date$
|
|
:: Revision: $Rev$
|
|
:: Author: $Author$
|
|
}
|
|
unit X2UtBinaryTree;
|
|
|
|
interface
|
|
uses
|
|
SysUtils,
|
|
VirtualTrees;
|
|
|
|
type
|
|
//:$ Raised when the cursor is invalid.
|
|
//:: Call Reset on the binary tree to create a valid cursor.
|
|
EX2UtBTreeInvalidCursor = class(Exception);
|
|
|
|
{
|
|
:$ Internal representation of a node.
|
|
}
|
|
PX2UtBTreeNode = ^TX2UtBTreeNode;
|
|
TX2UtBTreeNode = record
|
|
Index: Cardinal;
|
|
Parent: PX2UtBTreeNode;
|
|
Left: PX2UtBTreeNode;
|
|
Right: PX2UtBTreeNode;
|
|
Data: record end;
|
|
end;
|
|
|
|
{
|
|
:$ Internal parent stack
|
|
}
|
|
TX2UtBTreeStack = class(TObject)
|
|
private
|
|
FItems: array of PX2UtBTreeNode;
|
|
FCount: Integer;
|
|
FPosition: Integer;
|
|
public
|
|
constructor Create();
|
|
|
|
procedure Clear();
|
|
procedure Push(const ANode: PX2UtBTreeNode);
|
|
function Pop(): PX2UtBTreeNode;
|
|
|
|
procedure Reverse();
|
|
end;
|
|
|
|
{
|
|
:$ Binary tree implementation
|
|
|
|
:: This class implements a binary tree without knowing anything about
|
|
:: the data it contains.
|
|
}
|
|
TX2UtCustomBTree = class(TObject)
|
|
private
|
|
FRoot: PX2UtBTreeNode;
|
|
FCursor: PX2UtBTreeNode;
|
|
FIsReset: Boolean;
|
|
FParent: TX2UtBTreeStack;
|
|
|
|
FNodeSize: Cardinal;
|
|
FDataSize: Cardinal;
|
|
|
|
function GetTotalSize(): Cardinal;
|
|
protected
|
|
function GetCurrentIndex(): Cardinal;
|
|
|
|
function GetNodeData(const ANode: PX2UtBTreeNode): Pointer; virtual;
|
|
function LookupNode(const AIndex: Cardinal;
|
|
const ACanCreate: Boolean = False;
|
|
const ASetCursor: Boolean = False): PX2UtBTreeNode;
|
|
|
|
procedure InitNode(var ANode: PX2UtBTreeNode); virtual;
|
|
procedure FreeNode(var ANode: PX2UtBTreeNode); virtual;
|
|
|
|
procedure ClearCursor(); virtual;
|
|
function ValidCursor(const ARaiseError: Boolean = True): Boolean; virtual;
|
|
|
|
property Cursor: PX2UtBTreeNode read FCursor write FCursor;
|
|
property Root: PX2UtBTreeNode read FRoot;
|
|
property IsReset: Boolean read FIsReset write FIsReset;
|
|
property Parent: TX2UtBTreeStack read FParent;
|
|
|
|
property NodeSize: Cardinal read FNodeSize;
|
|
property TotalSize: Cardinal read GetTotalSize;
|
|
|
|
// Note: do NOT change DataSize after the first node has
|
|
// been created! This will result in an Access Violation!
|
|
property DataSize: Cardinal read FDataSize write FDataSize;
|
|
|
|
//:$ Returns the index at the current cursor location.
|
|
property CurrentIndex: Cardinal read GetCurrentIndex;
|
|
public
|
|
constructor Create(); virtual;
|
|
destructor Destroy(); override;
|
|
|
|
//:$ Clears the tree.
|
|
procedure Clear();
|
|
|
|
//:$ Deletes a node from the tree.
|
|
procedure Delete(const AIndex: Cardinal);
|
|
|
|
//:$ Checks if an index exists in the tree.
|
|
//:: If the ASetCursor parameter is set to True, the cursor will be
|
|
//:: positioned at the item if it is found.
|
|
function Exists(const AIndex: Cardinal; const ASetCursor: Boolean = False): Boolean;
|
|
|
|
//:$ Resets the node cursor.
|
|
//:: The node cursor can be used to traverse through the binary tree.
|
|
//:: Call Reset first, followed by Next to get the first item. You can
|
|
//:: continue to call Next until it returns false. The CurrentIndex and
|
|
//:: CurrentValue properties will only be valid within the traversal.
|
|
//:! Adding or removing items will result in a loss of the current cursor
|
|
//:! until the next Reset call.
|
|
procedure Reset(); virtual;
|
|
|
|
//:$ Moves the node cursor to the next node.
|
|
//:! The order in which nodes are traversed is from top to bottom, left
|
|
//:! to right. Do not depend on the binary tree to sort the output.
|
|
function Next(): Boolean; virtual;
|
|
end;
|
|
|
|
{
|
|
:$ Binary tree implementation for pointer values
|
|
}
|
|
TX2UtBTree = class(TX2UtCustomBTree)
|
|
private
|
|
function GetItem(Index: Cardinal): Pointer;
|
|
procedure SetItem(Index: Cardinal; const Value: Pointer);
|
|
|
|
function GetCurrentValue(): Pointer;
|
|
public
|
|
constructor Create(); override;
|
|
property CurrentIndex;
|
|
|
|
//:$ Gets or sets an item.
|
|
property Items[Index: Cardinal]: Pointer read GetItem
|
|
write SetItem; default;
|
|
|
|
//:$ Returns the value at the current cursor location
|
|
property CurrentValue: Pointer read GetCurrentValue;
|
|
end;
|
|
|
|
{
|
|
:$ Binary tree implementation for integer values
|
|
}
|
|
TX2UtIntegerBTree = class(TX2UtBTree)
|
|
protected
|
|
function GetItem(Index: Cardinal): Integer;
|
|
procedure SetItem(Index: Cardinal; const Value: Integer);
|
|
function GetCurrentValue(): Integer;
|
|
public
|
|
//:$ Gets or sets an item.
|
|
property Items[Index: Cardinal]: Integer read GetItem
|
|
write SetItem; default;
|
|
|
|
//:$ Returns the value at the current cursor location
|
|
property CurrentValue: Integer read GetCurrentValue;
|
|
end;
|
|
|
|
{
|
|
:$ Binary tree implementation for string values
|
|
}
|
|
TX2UtStringBTree = class(TX2UtCustomBTree)
|
|
protected
|
|
function GetItem(Index: Cardinal): String;
|
|
procedure SetItem(Index: Cardinal; const Value: String);
|
|
|
|
function GetCurrentValue(): String;
|
|
protected
|
|
procedure InitNode(var ANode: PX2UtBTreeNode); override;
|
|
procedure FreeNode(var ANode: PX2UtBTreeNode); override;
|
|
public
|
|
constructor Create(); override;
|
|
property CurrentIndex;
|
|
|
|
//:$ Gets or sets an item.
|
|
property Items[Index: Cardinal]: String read GetItem
|
|
write SetItem; default;
|
|
|
|
//:$ Returns the value at the current cursor location
|
|
property CurrentValue: String read GetCurrentValue;
|
|
end;
|
|
|
|
implementation
|
|
resourcestring
|
|
RSOrphanNode = 'BUG: Node does not seem to belong to it''s parent!';
|
|
RSInvalidCursor = 'Cursor is invalid!';
|
|
RSTooManyPops = 'More Pops than Pushes!';
|
|
RSInvalidDataSize = 'Invalid data size!';
|
|
|
|
const
|
|
CStackSize = 32;
|
|
|
|
|
|
{======================== TX2UtBTreeStack
|
|
Item Management
|
|
========================================}
|
|
constructor TX2UtBTreeStack.Create;
|
|
begin
|
|
inherited;
|
|
|
|
FCount := CStackSize;
|
|
FPosition := -1;
|
|
SetLength(FItems, FCount);
|
|
end;
|
|
|
|
|
|
procedure TX2UtBTreeStack.Clear;
|
|
begin
|
|
FCount := CStackSize;
|
|
FPosition := -1;
|
|
SetLength(FItems, FCount);
|
|
end;
|
|
|
|
procedure TX2UtBTreeStack.Push;
|
|
begin
|
|
Inc(FPosition);
|
|
if FPosition >= FCount then
|
|
begin
|
|
Inc(FCount, FCount);
|
|
SetLength(FItems, FCount);
|
|
end;
|
|
|
|
FItems[FPosition] := ANode;
|
|
end;
|
|
|
|
function TX2UtBTreeStack.Pop;
|
|
begin
|
|
Result := nil;
|
|
if FPosition > -1 then
|
|
begin
|
|
Result := FItems[FPosition];
|
|
Dec(FPosition);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TX2UtBTreeStack.Reverse;
|
|
var
|
|
iCount: Integer;
|
|
iIndex: Integer;
|
|
pSwap: PX2UtBTreeNode;
|
|
|
|
begin
|
|
if FPosition = -1 then
|
|
exit;
|
|
|
|
iCount := (FPosition + 1) div 2;
|
|
for iIndex := 0 to iCount - 1 do
|
|
begin
|
|
pSwap := FItems[iIndex];
|
|
FItems[iIndex] := FItems[FPosition - iIndex];
|
|
FItems[FPosition - iIndex] := pSwap;
|
|
end;
|
|
end;
|
|
|
|
|
|
{======================= TX2UtCustomBTree
|
|
Initialization
|
|
========================================}
|
|
constructor TX2UtCustomBTree.Create;
|
|
begin
|
|
inherited;
|
|
|
|
FParent := TX2UtBTreeStack.Create();
|
|
FNodeSize := SizeOf(TX2UtBTreeNode);
|
|
end;
|
|
|
|
destructor TX2UtCustomBTree.Destroy;
|
|
begin
|
|
FreeAndNil(FParent);
|
|
|
|
if Assigned(FRoot) then
|
|
FreeNode(FRoot);
|
|
|
|
inherited;
|
|
end;
|
|
|
|
|
|
{======================= TX2UtCustomBTree
|
|
Tree Management
|
|
========================================}
|
|
function TX2UtCustomBTree.GetNodeData;
|
|
begin
|
|
Assert(DataSize > 0, RSInvalidDataSize);
|
|
Result := Pointer(Cardinal(ANode) + NodeSize);
|
|
end;
|
|
|
|
function TX2UtCustomBTree.LookupNode;
|
|
var
|
|
pNode: PX2UtBTreeNode;
|
|
|
|
begin
|
|
Result := nil;
|
|
|
|
if not Assigned(FRoot) then
|
|
begin
|
|
if ACanCreate then
|
|
begin
|
|
InitNode(FRoot);
|
|
Result := FRoot;
|
|
|
|
if ASetCursor then
|
|
begin
|
|
Parent.Clear();
|
|
Cursor := FRoot;
|
|
end;
|
|
end;
|
|
|
|
exit;
|
|
end;
|
|
|
|
pNode := Root;
|
|
while Assigned(pNode) do
|
|
begin
|
|
if AIndex = pNode^.Index then
|
|
begin
|
|
Result := pNode;
|
|
break;
|
|
end else if AIndex < pNode^.Index then
|
|
begin
|
|
if Assigned(pNode^.Left) then
|
|
pNode := pNode^.Left
|
|
else
|
|
begin
|
|
if ACanCreate then
|
|
begin
|
|
InitNode(pNode^.Left);
|
|
Result := pNode^.Left;
|
|
Result^.Index := AIndex;
|
|
Result^.Parent := pNode;
|
|
end;
|
|
|
|
break;
|
|
end;
|
|
end else
|
|
begin
|
|
if Assigned(pNode^.Right) then
|
|
pNode := pNode^.Right
|
|
else
|
|
begin
|
|
if ACanCreate then
|
|
begin
|
|
InitNode(pNode^.Right);
|
|
Result := pNode^.Right;
|
|
Result^.Index := AIndex;
|
|
Result^.Parent := pNode;
|
|
end;
|
|
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if ASetCursor and Assigned(Result) then
|
|
begin
|
|
// Trace parents
|
|
Parent.Clear();
|
|
pNode := Result^.Parent;
|
|
while Assigned(pNode) do
|
|
begin
|
|
Parent.Push(pNode);
|
|
pNode := pNode^.Parent;
|
|
end;
|
|
|
|
// Parents are now in reverse order
|
|
Parent.Reverse();
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TX2UtCustomBTree.InitNode;
|
|
begin
|
|
Assert(DataSize > 0, RSInvalidDataSize);
|
|
GetMem(ANode, TotalSize);
|
|
FillChar(ANode^, TotalSize, #0);
|
|
end;
|
|
|
|
procedure TX2UtCustomBTree.FreeNode;
|
|
begin
|
|
if Assigned(ANode^.Left) then
|
|
FreeNode(ANode^.Left);
|
|
|
|
if Assigned(ANode^.Right) then
|
|
FreeNode(ANode^.Right);
|
|
|
|
if Assigned(ANode^.Parent) then
|
|
if ANode^.Parent^.Left = ANode then
|
|
ANode^.Parent^.Left := nil
|
|
else if ANode^.Parent^.Right = ANode then
|
|
ANode^.Parent^.Right := nil
|
|
else
|
|
Assert(False, RSOrphanNode);
|
|
|
|
FreeMem(ANode, TotalSize);
|
|
ClearCursor();
|
|
|
|
ANode := nil;
|
|
end;
|
|
|
|
|
|
procedure TX2UtCustomBTree.Clear;
|
|
begin
|
|
if Assigned(FRoot) then
|
|
FreeNode(FRoot);
|
|
end;
|
|
|
|
procedure TX2UtCustomBTree.Delete;
|
|
var
|
|
pItem: PX2UtBTreeNode;
|
|
|
|
begin
|
|
pItem := LookupNode(AIndex);
|
|
if Assigned(pItem) then
|
|
FreeNode(pItem);
|
|
end;
|
|
|
|
function TX2UtCustomBTree.Exists;
|
|
begin
|
|
Result := Assigned(LookupNode(AIndex, False, True));
|
|
end;
|
|
|
|
|
|
|
|
{======================= TX2UtCustomBTree
|
|
Tree Traversing
|
|
========================================}
|
|
function TX2UtCustomBTree.ValidCursor;
|
|
begin
|
|
Result := (Assigned(Cursor) and (not IsReset));
|
|
|
|
if (not Result) and (ARaiseError) then
|
|
raise EX2UtBTreeInvalidCursor.Create(RSInvalidCursor);
|
|
end;
|
|
|
|
procedure TX2UtCustomBTree.ClearCursor;
|
|
begin
|
|
Cursor := nil;
|
|
end;
|
|
|
|
|
|
procedure TX2UtCustomBTree.Reset;
|
|
begin
|
|
Cursor := Root;
|
|
IsReset := True;
|
|
end;
|
|
|
|
function TX2UtCustomBTree.Next;
|
|
var
|
|
pParent: PX2UtBTreeNode;
|
|
pCurrent: PX2UtBTreeNode;
|
|
|
|
begin
|
|
Result := False;
|
|
|
|
if not Assigned(Cursor) then
|
|
begin
|
|
IsReset := False;
|
|
exit;
|
|
end;
|
|
|
|
if not IsReset then
|
|
begin
|
|
if Assigned(Cursor^.Left) then
|
|
begin
|
|
// Valid left path, follow it
|
|
Parent.Push(Cursor);
|
|
Cursor := Cursor^.Left;
|
|
Result := True;
|
|
end else if Assigned(Cursor^.Right) then
|
|
begin
|
|
// Valid right path, follow it
|
|
Parent.Push(Cursor);
|
|
Cursor := Cursor^.Right;
|
|
Result := True;
|
|
end else
|
|
begin
|
|
// Neither is valid, traverse back up the parent stack until
|
|
// a node if found with a sibling
|
|
pCurrent := Cursor;
|
|
pParent := Parent.Pop();
|
|
ClearCursor();
|
|
|
|
while Assigned(pParent) do
|
|
begin
|
|
if Assigned(pParent^.Right) and (pParent^.Right <> pCurrent) then
|
|
begin
|
|
// Parent has a sibling, follow it
|
|
Parent.Push(pParent);
|
|
Cursor := pParent^.Right;
|
|
Result := True;
|
|
break;
|
|
end;
|
|
|
|
pCurrent := pParent;
|
|
pParent := Parent.Pop();
|
|
end;
|
|
end;
|
|
end else
|
|
begin
|
|
IsReset := False;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TX2UtCustomBTree.GetCurrentIndex;
|
|
begin
|
|
Result := 0;
|
|
if ValidCursor(True) then
|
|
Result := Cursor^.Index;
|
|
end;
|
|
|
|
function TX2UtCustomBTree.GetTotalSize;
|
|
begin
|
|
Result := FNodeSize + FDataSize;
|
|
end;
|
|
|
|
|
|
{============================= TX2UtBTree
|
|
Item Management
|
|
========================================}
|
|
constructor TX2UtBTree.Create;
|
|
begin
|
|
inherited;
|
|
|
|
DataSize := SizeOf(Pointer);
|
|
end;
|
|
|
|
function TX2UtBTree.GetItem;
|
|
var
|
|
pNode: PX2UtBTreeNode;
|
|
|
|
begin
|
|
Result := nil;
|
|
pNode := LookupNode(Index);
|
|
if Assigned(pNode) then
|
|
Result := PPointer(GetNodeData(pNode))^;
|
|
end;
|
|
|
|
procedure TX2UtBTree.SetItem;
|
|
var
|
|
pNode: PX2UtBTreeNode;
|
|
|
|
begin
|
|
pNode := LookupNode(Index, True);
|
|
if Assigned(pNode) then
|
|
PPointer(GetNodeData(pNode))^ := Value;
|
|
end;
|
|
|
|
function TX2UtBTree.GetCurrentValue;
|
|
begin
|
|
Result := nil;
|
|
if ValidCursor(True) then
|
|
Result := PPointer(GetNodeData(Cursor))^;
|
|
end;
|
|
|
|
|
|
{====================== TX2UtIntegerBTree
|
|
Item Management
|
|
========================================}
|
|
function TX2UtIntegerBTree.GetItem;
|
|
begin
|
|
Result := Integer(inherited GetItem(Index));
|
|
end;
|
|
|
|
procedure TX2UtIntegerBTree.SetItem;
|
|
begin
|
|
inherited SetItem(Index, Pointer(Value));
|
|
end;
|
|
|
|
function TX2UtIntegerBTree.GetCurrentValue;
|
|
begin
|
|
Result := Integer(inherited GetCurrentValue());
|
|
end;
|
|
|
|
|
|
{======================= TX2UtStringBTree
|
|
Item Management
|
|
========================================}
|
|
constructor TX2UtStringBTree.Create;
|
|
begin
|
|
inherited;
|
|
|
|
DataSize := SizeOf(PString);
|
|
end;
|
|
|
|
|
|
procedure TX2UtStringBTree.InitNode;
|
|
var
|
|
pData: PString;
|
|
|
|
begin
|
|
inherited;
|
|
|
|
pData := GetNodeData(ANode);
|
|
Initialize(pData^);
|
|
end;
|
|
|
|
procedure TX2UtStringBTree.FreeNode;
|
|
var
|
|
pData: PString;
|
|
|
|
begin
|
|
pData := GetNodeData(ANode);
|
|
Finalize(pData^);
|
|
|
|
inherited;
|
|
end;
|
|
|
|
|
|
function TX2UtStringBTree.GetItem;
|
|
var
|
|
pNode: PX2UtBTreeNode;
|
|
|
|
begin
|
|
pNode := LookupNode(Index);
|
|
if Assigned(pNode) then
|
|
Result := PString(GetNodeData(pNode))^;
|
|
end;
|
|
|
|
procedure TX2UtStringBTree.SetItem;
|
|
var
|
|
pNode: PX2UtBTreeNode;
|
|
|
|
begin
|
|
pNode := LookupNode(Index, True);
|
|
if Assigned(pNode) then
|
|
PString(GetNodeData(pNode))^ := Value;
|
|
end;
|
|
|
|
function TX2UtStringBTree.GetCurrentValue;
|
|
begin
|
|
if ValidCursor(True) then
|
|
Result := PString(GetNodeData(Cursor))^;
|
|
end;
|
|
|
|
end.
|