Contributor: JEFF ATWOOD

unit Tlink;
{ TLink unit: doubly linked lists 5/22/95}
{ by Jeff Atwood, JAtwood159@AOL.COM. }
{ }
{ This unit can be used for stacks, deques, and free lists too.  }
{ }
{ I couldn't find a doubly-linked list implemented as an object ANYWHERE }
{ so I wrote it myself, after much trial, error, and poring over         }
{ obscure programming reference books. Hey-- it's not brain surgery, but }
{ pointers can be so naughty. }
{ }
{ For simplicity's sake, and to keep this a one-day project, I am only    }
{ storing simple integers in the cells. You can easily, easily change     }
{ that to any data type supported by Delphi including records. I would    }
{ NOT recommend trying to store a whole object with methods in there...   }
{ I couldn't get that to work. But if you can, E-Mail me. I don't know if }
{ it's even possible. }
{ }
{ There is one main object, which uses the "CELL" record type for each    }
{ entry in the list. I don't know how to hide the CELL record type from   }
{ the user, but it should be internal to this unit. The main object is    }
{ the TLink, which keeps track of the size, first, last, and current      }
{ cell records. You can move around in the list by using the Move methods }
{ and find using the Seek method. It's all fairly straightforward, look   }
{ at the demo form for examples, there are also comments in the code.     }
{ }
{ If you're feeling ambitious, I recommend you modify the cell record to  }
{ store pointers instead of integers. Don't forget to make copies of the  }
{ data, because if you point to the actual location, you're screwed when  }
{ the user destroys that instance. You gotta copy it... How many times    }
{ did I get burned by THAT one?? Also, it would be cool to turn this into }
{ a VCL component, if anyone wants to do that. }
{ }
{ This code is freeware. Please E-Mail me any cool additions, bug fixes,  }
{ rants, raves, etc. at JAtwood159@AOL.COM! Thanks for trying my code, I  }
{ hope it helps someone... }
interface
type
  CellPtr = ^Cell;
  Cell = record
    data: Integer;
    next: CellPtr;
    prev: CellPtr;
  end;
  TList = class(TObject)
  private
    top: CellPtr;
    bottom: CellPtr;
    current: CellPtr;
    size: Longint;
  public
    constructor create;
    destructor destroy; override;
    function IsEmpty: Boolean;
    function GetSize: Longint;
    procedure InsertBottom(item: Integer);
    procedure InsertTop(item: Integer);
    function InsertCurrent(item: Integer): Boolean;
    function FindFirst(item: Integer; var absLoc: longint): Boolean;
    function Delete: Boolean;
    function MoveFirst: Boolean;
    function MoveLast: Boolean;
    function MoveNext: Boolean;
    function MovePrevious: Boolean;
    function Seek(absLoc: longint): Boolean;
    function GetData(var item: Integer): Boolean;
  end;
implementation
{ set up the TList object with default values }
constructor TList.create;
begin
  inherited create;
  top := nil;
  bottom := nil;
  current := nil;
  size := 0;
end;
{ destroy the entire list, cell by cell }
destructor TList.destroy;
var
  curCell: CellPtr;
  nextCell: CellPtr;
begin
  curCell := top;
  while not (curCell = nil) do begin
    nextCell := curCell^.next;
    freemem(curCell, SizeOf(Cell));
    curCell := nextCell;
  end;
  top := nil;
  bottom := nil;
  current := nil;
  inherited destroy;
end;
{ returns true if the list has no cells }
function TList.isEmpty: Boolean;
begin
  result := (size = 0);
end;
{ returns number of cells in list }
function TList.getSize: Longint;
begin
  result := size;
end;
{ insert cell at bottom of list }
procedure TList.InsertBottom(item: Integer);
var
  newCell: CellPtr;
begin
  GetMem(newCell, Sizeof(Cell));
  newCell^.data := item;
  newCell^.prev := bottom;
  newCell^.next := nil;
  { special case: this is first cell added }
  if bottom = nil then
    top := newCell
  else
    bottom^.next := newCell;
  bottom := newCell;
  size := size + 1;
end;
{ insert cell at top of list }
procedure TList.InsertTop(item: Integer);
var
  newCell: CellPtr;
begin
  GetMem(newCell, Sizeof(Cell));
  newCell^.data := item;
  newCell^.prev := nil;
  newCell^.next := top;
  { special case: this is first cell added }
  if top = nil then
    bottom := newCell
  else
    top^.prev := newCell;
  top := newCell;
  size := size + 1;
end;
{ insert cell after current item }
function TList.InsertCurrent(item: Integer): Boolean;
var
  newCell: CellPtr;
begin
  if (current = nil) then
    result := False
  else begin
    GetMem(newCell, Sizeof(Cell));
    newCell^.data := item;
    newCell^.prev := current;
    newCell^.next := current^.next;
    { special case: current cell is last cell }
    if current^.next = nil then
      bottom := newCell
    else
      current^.next^.prev := newCell;
    current^.next := newCell;
    size := size + 1;
    result := True;
  end;
end;
{ Look for item in data field. Starts at top of list }
{ and looks at every item until a match is found.    }
{ if found, makes matched cell current, and returns  }
{ absolute location of match where 1 = top.          }
function TList.FindFirst(item: Integer; var absLoc: longint): Boolean;
var
  curCell: CellPtr;
  cnt: longInt;
begin
  result := False;
  curCell := top;
  cnt := 0;
  absLoc := 0;
  while not (curCell = nil) do begin
    cnt := cnt + 1;
    if curCell^.Data = item then begin
      absLoc := cnt;
      current := curCell;
      result := True;
      exit;
    end;
    curCell := curCell^.next;
  end;
end;

{ delete the current cell }
function TList.Delete: Boolean;
label
  exitDelete;
begin
  { we can only delete the current record }
  if current = nil then
    result := False
  else begin
    { see if list has one item }
    if size = 1 then begin
      top := nil;
      bottom := nil;
      goto exitDelete;
    end;
    { see if we're at the top of list }
    if current^.prev = nil then begin
      top := current^.next;
      top^.prev := nil;
      goto exitDelete;
    end;
    { see if we're at the bottom of list }
    if current^.next = nil then begin
      bottom := current^.prev;
      bottom^.next := nil;
      goto exitDelete;
    end;
    { we must be in middle of list of size > 1 }
    current^.prev^.next := current^.next;
    current^.next^.prev := current^.prev;
    goto exitDelete;
  end;
  { arrgh-- a goto! but this is a textbook goto! }
  exitDelete: begin
                result := True;
                freemem(current, SizeOf(Cell));
                current := nil;
                size := size - 1;
                if size = 0 then begin
                  top := nil;
                  bottom := nil;
                end;
              end;
end;
{ make first value in list current }
function TList.MoveFirst: Boolean;
begin
  if top = nil then
    result := False
  else begin
    current := top;
    result := True;
  end;
end;
{ make last value in list current }
function TList.MoveLast: Boolean;
begin
  if bottom = nil then
    result := False
  else begin
    current := bottom;
    result := True;
  end;
end;
{ make next value in list current }
function TList.MoveNext: Boolean;
begin
  if (current = nil) or (current^.next = nil) then
    result := False
  else begin
    current := current^.next;
    result := True;
  end
end;
{ make previous value in list current }
function TList.MovePrevious: Boolean;
begin
  if (current = nil) or (current^.prev = nil) then
    result := False
  else begin
    current := current^.prev;
    result := True;
  end;
end;
{ return data item from current list position }
function TList.GetData(var item: Integer): Boolean;
begin
  if (current = nil) then
    result := False
  else begin
    item := current^.data;
    result := True;
  end;
end;
{ make current the absolute cell N in the list }
{ where top = 1 }
function TList.Seek(absloc: longint): Boolean;
var
  curCell: CellPtr;
  cnt: longint;
begin
  result := False;
  if absloc <= 0 then
    exit;
  curCell := top;
  while not (curCell = nil) do begin
    cnt := cnt + 1;
    if cnt = absloc then begin
      current := curCell;
      result := True;
      exit;
    end;
    curCell := curCell^.next;
  end;
end;
end.